import asyncio
import logging
import os
import re

from functools import partial
from threading import Thread

from pydantic import BaseModel
from typing import List, Tuple, Optional, Type, Literal, Dict
from datetime import datetime, timezone, timedelta

import serial_asyncio
import aiohttp


import time
import copy
import subprocess
from uuid import uuid4

from .routers import reboot_power
from .schemas.ad_ul_dl import AdUlDl, UlDl
from .schemas.modem_state import ModemState

from common.third_party.devmem import DevMem
from common.third_party.save_config import save_config, load_config, load_const
from services.server_integration.schemas.full_unit_configuration import FullUnitConfiguration, UnitConfiguration,\
    RelayChannel, ServiceChannel, AlarmConfig, Modem
from services.server_integration.schemas.relay_channel_configuration import Rtr, RtrCommon, Link as CoLink
from services.server_integration.schemas.service_channel_configuration import NbSubchannels, Str
from services.server_integration.schemas.unit_configuration import CalibrationState
from services.server_integration.schemas.technical_status import TechnicalStatus, TSValue, \
    TSOcbStates, TSSfpModule, TSGpsTelemetry, TSPwrTelemetry, TSDspTelemetry, \
    TSSmTelemetry, TSRmTelemetry
from services.server_integration.schemas.calibrate_status import CalibrateMessage
from services.server_integration.schemas.power_levels import PowerLevels
from services.server_integration.schemas.ac_magic_numbers import ACMagicNumbers
from .schemas.alarms import Alarm, AlarmDescription, AlarmInfo, AlarmsContext, LanguageString, AlarmsGroupCount, \
    ALARMS, ALARM_CODE_BY_NAME, ALARM_NAME_BY_CODE, CURRENT_TIME_POINT
from .schemas.alarms_request import AlarmsRequest, AlarmsRequestCount, Filter as AlarmFilter
from services.control.api import ControlApi
from services.dsp.api import DspApi
from services.dsp.schemas.configs import Delays, DspConfig
from services.attenuators.api import AttenuatorsApi, AttenuatorsConfig
from services.ad936x.api import Ad936xApi, DoubleFrequency, DoubleConfig, \
    DoubleGain, SingleGain, DoubleAttenuation, SingleAttenuation
from services.relays_module.api import RelaysModuleApi, RmConfig
from services.automatic_control.api import AutomaticControlApi
from services.automatic_control.schemas.configs import OutputPowerLinkConfig, BSPower, ServicesOutPower
from services.server_integration.util import clamp
from services.services_module.api import ServicesModuleApi
from services.services_module.schemas.configs import ModuleType
from services.telemetry.api import TelemetryApi
from services.ocb.api import OcbApi
from .alarm_db import AlarmDB, BlockVersionsDB
from .timezone_db import TimezoneDB
from settings import settings
from .schemas.alarms_response_configuration import AlarmResponseConfiguration
from .schemas.alarms import OldestNewestAlarmsDate, ServerAlarm
from .schemas.cities_timezone import CityTimezone, Timezones, TimezonesRequest, CityByTimezoneRequest
from common.third_party.save_config import save_config, load_config, save_const, load_const
from services.attenuators.schemas.configs import AttenuatorsConfig
from services.server_integration.router_api import ServerIntegrationRouterApi
from common.third_party.log import new_logger
from services.new_services.new_server_integration.schemas.zcalibration import ZCalibrationTelemetry, ZCalibrationInfo,\
    ZCalibrationStatus, ZCalibrationDevice
from services.new_services.new_server_integration.cviya import _get_config_by_cviya, ResponseMessage
from services.server_integration.schemas.modem_config import ModemConfig

from multiprocessing.connection import Client
from services.server_integration.schemas.alarms import BlockAlarmType
from common.third_party.one_shot_patcher import OneShotPatcher
from services.server_integration.patches import startup_patch_12_10_23, startup_patch_19_10_23, update_updater,\
    start_up_patch_11_12_23, clean_alarms_10_04_24, update_wdialirz_daemon, update_wtelitncm_daemon,\
    start_up_patch_set_unit_config_by_technic, enable_old_frontend, remote_link_dl_tx_pack, reboot_patch, update_linux,\
    reboot_patch, reboot_power_patch, disable_garbage_services, yog_sothoth_systemd_log_fix, update_kintex

FILTHY_SM_SERIAL_NUMBERS_12 = [
    '003-03 2303 2',
    '003-03 2303 4',
    '003-03 2303 5'
]

FILTHY_SM_SERIAL_NUMBERS_34 = [
    '003-05 2303 2',
    '003-04 2303 1'
]


class ServerIntegration:
    __slots__ = ['full_unit_config', 'technical_status', 'current_alarms',
                 'alarms_context', 'storager', 'alarm_db', 'timezone_db',
                 'telemetry_lock', 'rtr_normal_vswr', 'server_status', 'unsent_alarms', 'unsent_alarms2',
                 '_server_logger', '_c_lock', '_sir_api',
                 '_invalid_modem_time', '_invalid_sim_time',
                 '_blocked_alarms', "_update_ip_address_sync_loop_task", '_is_time_synchronized',
                 '_server_time_sync_time', '_what_demon_is_used_flag', '_check_mode_counter', '_off_simulation_task',
                 'gps_time_sync', 'hands_time_sync', 'server_time_sync', 'block_version_db'
                 ]

    def __init__(self, loop) -> None:
        self._blocked_alarms: set[int] = set()
        self.full_unit_config = self.load_full_config()
        self.technical_status = TechnicalStatus()

        self.unsent_alarms: List[Optional[Alarm]] = load_config('si/unsent_alarms', List[Optional[Alarm]], [])
        self.unsent_alarms2: Dict[int, Tuple[Optional[Alarm], Optional[Alarm]]] =\
            load_config(
                'si/unsent_alarms2',
                Dict[int, Tuple[Optional[Alarm], Optional[Alarm]]],
                {}
            )

        for _ in range(len(self.unsent_alarms)):
            try:
                self.append_unsent_alarm(self.unsent_alarms.pop())
            except Exception:
                break

        save_config('si/unsent_alarms', self.unsent_alarms)
        self.current_alarms: List[Optional[Alarm]] = load_config('si/current_alarms', List[Optional[Alarm]], [None] * len(ALARMS))
        if len(self.current_alarms) < len(ALARMS):
            delta = len(ALARMS) - len(self.current_alarms)
            self.current_alarms.extend([None] * delta)

        self.rtr_normal_vswr: list[float] = load_const('si/rtr_normal_vswr', list[float], [500] * 2)
        self.alarms_context = AlarmsContext(startup_time=datetime.utcnow())

        self._invalid_modem_time = load_config('si/invalid_modem_time', float, 0)
        self._invalid_sim_time = load_config('si/invalid_sim_time', float, 0)

        self.alarm_db = AlarmDB(settings.DATABASE_PATH + '/test.db')
        self.alarm_db.make_current_time_point()
        self.alarm_db.close_power_alarms()
        self.timezone_db = TimezoneDB(settings.DATABASE_PATH + '/timezone-cities.db')
        self.block_version_db = BlockVersionsDB(settings.DATABASE_PATH + '/block_versions.db')

        if load_config('si/is_updated', bool, False):
            previous_version: str = load_config('si/current_version', str, None)

            self.raise_info('UserUpdatedBlock', previous_version, settings.APP_VERSION)
            save_config('si/current_version', settings.APP_VERSION)
            save_config('si/is_updated', False)

        else:
            save_config('si/current_version', settings.APP_VERSION)

        self.alarm_db.update_alarms_description(self.load_alarms_description())
        self.telemetry_lock = None
        self.server_status = True
        self._is_time_synchronized = False

        self.gps_time_sync = False
        self.hands_time_sync = False
        self.server_time_sync = False

        self._server_time_sync_time = None

        self.raise_alarm('PowerFailure', False, value=False)

        self._server_logger = new_logger('server-ping', logging.ERROR)

        self._c_lock = asyncio.Lock()

        self._sir_api = ServerIntegrationRouterApi()

        self._update_ip_address_sync_loop_task = Thread(target=self.update_ip_address_sync_loop)
        self._update_ip_address_sync_loop_task.start()

        self._what_demon_is_used_flag = False
        self._check_mode_counter = 0

        self._off_simulation_task = None

        save_config('tm/gps_set_time_time', 0.0)

        patcher = OneShotPatcher()
        patcher.patch('601ab1f6-13e8-4a04-95ad-a97529180e78', startup_patch_12_10_23, self)
        patcher.patch('e1e8710d-94cc-4410-9025-08f069449499', startup_patch_19_10_23, self)
        patcher.patch('033ece3d-9609-4c44-8b37-3ab1672ff18a', start_up_patch_11_12_23, self)
        patcher.patch('7f7916c2-84ff-4253-bd1b-5f8617dceeaa', update_updater)
        patcher.patch('45733ea5-dda7-49a7-893c-36259e0ee371', update_wdialirz_daemon)
        patcher.patch('435d77df-14d2-4e2b-8f9b-0d59bb245dfe', update_wtelitncm_daemon)
        patcher.patch('e0727ffe-00bf-496d-98b6-8f594fd36978', enable_old_frontend)
        patcher.patch('ea9510c9-cd49-4722-b2cc-14c49b7efd73', remote_link_dl_tx_pack)
        patcher.patch('8613edb2-a3e7-4905-9a01-ee4ca5a8a7f1', disable_garbage_services)
        patcher.patch('6e35ca0e-0e79-455b-9bb3-001b6dadbc99', yog_sothoth_systemd_log_fix)
        patcher.patch("086f72da-eff5-4245-a848-e2c829da38fa", update_kintex)
        patcher.reboot_patch("b156cf6f-3b6b-445e-affa-d3fc73c860d2")
        # patcher.patch('4ac9ceb5-2c05-42a7-b279-9159cf5b5499', reboot_patch)
        # patcher.patch('c2b74a51-b640-4092-b57a-75f1e5c04406', update_linux)
        # patcher.patch('7a09ceb5-2cc5-42a7-b279-9159cf5b5499', reboot_power_patch)

        # patcher.patch('23c25312-ec1c-48fb-b41c-3997d800f996', clean_alarms_10_04_24, self.alarm_db._connection)
        patcher.patch_late(loop, 32, 'acd501fd-12a9-45dc-883d-8068893ff87e', start_up_patch_set_unit_config_by_technic, self)

    async def send_start_up_signal_to_frontend_loop(self):
        await asyncio.sleep(20)
        while True:
            try:
                with Client(('localhost', 8777), authkey=b'up-up-down-down-left-right-left-right-a-b-7') as client:
                    client.send('start')
                    return
            except:
                pass

            await asyncio.sleep(50)

    async def zcalibration_calibrate(self, index: int, target_power: float) -> ZCalibrationStatus:
        block_state = self.full_unit_config.unit_configuration.unit_type == 'FSC'
        cviya = self.full_unit_config.unit_configuration.cviya
        cviya_info = _get_config_by_cviya(cviya)
        if isinstance(cviya_info, ResponseMessage):
            return ZCalibrationStatus.Moonshine

        full_config_save = self.full_unit_config
        new_full_config = full_config_save.copy(deep=True)

        for idx in range(2):
            new_full_config.set_rm_enable(idx, False)
            new_full_config.set_optic_enable(idx, False)
        for idx in range(settings.SERVICES_VIRTUAL_RELAYS_NUMBER):
            new_full_config.set_relay_enabled(idx, False)
        for idx in range(settings.SERVICES_MODULE_NUMBER):
            new_full_config.service_channels[idx].str.enabled = False

        if index in (0, 1):
            li = index

            new_full_config.set_rm_enable(li, True)
            new_full_config.set_optic_enable(li, False)

            if block_state and cviya == 'ЦВИЯ.464117.004-02 РБ':
                new_full_config.set_rm_central_frequency(index, 6_655_000_000, 6_315_000_000)
            elif not block_state and cviya == 'ЦВИЯ.464117.003-03 РБ':
                new_full_config.set_rm_central_frequency(index, 6_315_000_000, 6_655_000_000)
            elif block_state or cviya == 'ЦВИЯ.464117.003-05':
                new_full_config.set_rm_central_frequency(li, 7_020_000_000)
            else:
                new_full_config.set_rm_central_frequency(li, 6_680_000_000)

            new_full_config.set_relay_enabled(0, True, True)
            new_full_config.set_relay_enabled(1, True, True)
            new_full_config.set_relay_frequency_offset(0, -10_000_000, -10_000_000)
            new_full_config.set_relay_frequency_offset(1, 10_000_000, 10_000_000)
            new_full_config.set_relay_bandwidth(0, 20_000_000, 20_000_000)
            new_full_config.set_relay_bandwidth(1, 20_000_000, 20_000_000)

            new_full_config.service_channels[0].str.enabled = True
            new_full_config.service_channels[1].str.enabled = True
            new_full_config.service_channels[0].bandwidth = 20_000_000
            new_full_config.service_channels[1].bandwidth = 20_000_000

        elif not block_state and index in (2, 3, 4, 5):
            li = index - 2

            new_full_config.service_channels[li].str.enabled = True
            new_full_config.service_channels[li].bandwidth = 20_000_000
            sm_freq = cviya_info.sms_freq[li]
            if sm_freq == 2100:
                new_full_config.service_channels[li].str.carrier_frequency.dl = 2_140_000_000
            elif sm_freq == 1800:
                new_full_config.service_channels[li].str.carrier_frequency.dl = 1_842_000_000
            elif sm_freq == 900:
                new_full_config.service_channels[li].str.carrier_frequency.dl = 942_000_000

            for relay_index in [0, li]:
                new_full_config.set_relay_enabled(relay_index, True, True)
                new_full_config.set_relay_frequency_offset(relay_index, 0, 0)
                new_full_config.set_relay_bandwidth(relay_index, 20_000_000, 20_000_000)
        elif block_state and index in (2, 3, 4, 5):
            return ZCalibrationStatus.Sunshine

        await self.process_full_unit_config(new_full_config, False, full_config_save, True)
        status = await self.special_calibrate(index, target_power)
        return (
            ZCalibrationStatus.Moonshine,
            ZCalibrationStatus.Sunshine
        )[int(status)]

    async def zcalibration_telemetry(self, index: int) -> ZCalibrationTelemetry:
        ac = AutomaticControlApi()
        ad_api = Ad936xApi()
        control_api = ControlApi()

        block_state = await control_api.get_control_state('block_state')
        rm_configs = await ad_api.get_config(0)
        sm_configs = await ad_api.get_config(1), await ad_api.get_config(2)
        # if block_state:
        #     gains = [
        #         70 - int(rm_configs.attenuation_0), 70 - int(rm_configs.attenuation_1),
        #         sm_configs[0].gain_0, sm_configs[0].gain_1,
        #         sm_configs[1].gain_0, sm_configs[1].gain_1
        #     ]
        #     attenuation = [
        #         70 - int(rm_configs.attenuation_0), 70 - int(rm_configs.attenuation_1),
        #         70 - int(sm_configs[0].attenuation_0), 70 - int(sm_configs[0].attenuation_1),
        #         70 - int(sm_configs[1].attenuation_0), 70 - int(sm_configs[1].attenuation_1)
        #     ]
        # else:
        gains = [
            70 - int(rm_configs.attenuation_0), 70 - int(rm_configs.attenuation_1),
            sm_configs[0].gain_1, sm_configs[0].gain_0,
            sm_configs[1].gain_1, sm_configs[1].gain_0
        ]
        attenuation = [
            70 - int(rm_configs.attenuation_0), 70 - int(rm_configs.attenuation_1),
            70 - int(sm_configs[0].attenuation_1), 70 - int(sm_configs[0].attenuation_0),
            70 - int(sm_configs[1].attenuation_1), 70 - int(sm_configs[1].attenuation_0)
        ]

        return ZCalibrationTelemetry(
            gain_dl=gains[index] if block_state else attenuation[index],
            output_power=await ac.get_current_output_power(index),
            power=await ac.read_true_power(index)
        )

    async def zcalibration_info(self, index: int) -> ZCalibrationInfo:
        ac = AutomaticControlApi()
        return ZCalibrationInfo(
            offsets=await ac.get_additional_output_power_correct_number(index),
        )

    async def zcalibration_save(self, index: int, info: ZCalibrationInfo):
        control_api = ControlApi()
        block_type = await control_api.get_control_state('block_state')
        if None in (info.target_power, info.real_power):
            return

        if block_type and 2 <= index <= 5:
            additional_correct_number = info.target_power - info.real_power
        else:
            additional_correct_number = info.real_power - info.target_power

        ac = AutomaticControlApi()
        await ac.set_additional_output_power_correct_number(index, additional_correct_number)

    def set_rtr_normal_vswr(self, index: int, vswr: float):
        self.rtr_normal_vswr[index] = vswr
        save_const('/si/rtr_normal_vswr', self.rtr_normal_vswr)

    def get_rtr_normal_vswr(self, index: int) -> float:
        return self.rtr_normal_vswr[index]

    async def calibrate_rtr_vswr(self, index) -> float | None:
        block_state = self.full_unit_config.unit_configuration.unit_type == 'FSC'
        cviya = self.full_unit_config.unit_configuration.cviya
        full_config_save = self.full_unit_config
        new_full_config = full_config_save.copy(deep=True)

        for idx in range(2):
            new_full_config.set_rm_enable(idx, False)
            new_full_config.set_optic_enable(idx, False)
        for idx in range(settings.SERVICES_VIRTUAL_RELAYS_NUMBER):
            new_full_config.set_relay_enabled(idx, False)
        for idx in range(settings.SERVICES_MODULE_NUMBER):
            new_full_config.service_channels[idx].str.enabled = False

        new_full_config.set_rm_enable(index, True)
        new_full_config.set_optic_enable(index, False)
        if block_state and cviya == 'ЦВИЯ.464117.004-02 РБ':
            new_full_config.set_rm_central_frequency(index, 6_655_000_000, 6_315_000_000)
        elif not block_state and cviya == 'ЦВИЯ.464117.003-03 РБ':
            new_full_config.set_rm_central_frequency(index, 6_315_000_000, 6_655_000_000)
        elif block_state or cviya == 'ЦВИЯ.464117.003-05':
            new_full_config.set_rm_central_frequency(index, 7_020_000_000)
        else:
            new_full_config.set_rm_central_frequency(index, 6_680_000_000)

        new_full_config.set_relay_enabled(0, True, True)
        new_full_config.set_relay_enabled(1, True, True)
        new_full_config.set_relay_frequency_offset(0, -10_000_000, -10_000_000)
        new_full_config.set_relay_frequency_offset(1, 10_000_000, 10_000_000)
        new_full_config.set_relay_bandwidth(0, 20_000_000, 20_000_000)
        new_full_config.set_relay_bandwidth(1, 20_000_000, 20_000_000)

        new_full_config.service_channels[0].str.enabled = True
        new_full_config.service_channels[1].str.enabled = True
        new_full_config.service_channels[0].bandwidth = 20_000_000
        new_full_config.service_channels[1].bandwidth = 20_000_000

        await self.process_full_unit_config(new_full_config, False, full_config_save)

        ac_api = AutomaticControlApi()
        return await ac_api.link_vswr_calibrate(index)

    def save_only_full_config(self, config: FullUnitConfiguration):
        self.full_unit_config = config
        save_config('si/full_unit_config', config)

    def update_invalid_modem_time(self):
        self._invalid_modem_time = datetime.now().timestamp()
        save_config('si/invalid_modem_time', self._invalid_modem_time)

    def is_reboot_available_by_invalid_modem(self):
        try:
            delta = datetime.now() - datetime.fromtimestamp(self._invalid_modem_time)
            return delta.total_seconds() > 22 * 60 * 60
        except Exception:
            return False
    def update_invalid_sim_time(self):
        self._invalid_sim_time = datetime.now().timestamp()
        save_config('si/invalid_sim_time', self._invalid_sim_time)

    def is_reboot_available_by_invalid_sim(self):
        try:
            delta = datetime.now() - datetime.fromtimestamp(self._invalid_sim_time)
            return delta.total_seconds() > 22 * 60 * 60
        except Exception:
            return False

    @staticmethod
    def save_full_config(config: FullUnitConfiguration, is_adjuster=False):
        if is_adjuster:
            save_const('full_unit_config', config)
            save_const('block_type', config.unit_configuration.unit_type)

        save_config('si/full_unit_config', config)

        server_info = config.unit_configuration.server
        if server_info.port < 100:
            server_address = f'{server_info.ip}'
        else:
            server_address = f'{server_info.ip}:{server_info.port}'

        save_config('plb/log_address', f'http://{server_address}/alarms/set-log-alarm/')
        save_config('plb/current_address', f'http://{server_address}/alarms/set-current-alarm/')
        save_config('plb/block_ip', config.modem.sim_ip)
        save_config('plb/block_name', config.unit_configuration.block_name)
        save_config('plb/uuid', config.unit_configuration.uuid)

        for index, (rm_enabled, op_enabled) in enumerate(zip(config.relay_channels[0].rtr_common.rt_rxs_enabled, config.relay_channels[0].rtr_common.optic_enabled)):
            save_config(f'dma/link_{index}', rm_enabled or op_enabled)

        for index, chanel in enumerate(config.service_channels):
            save_config(f'dma/service_{index}', chanel.str.enabled)

        #print(f'file saved at: {datetime.utcnow()}')

    async def ping_some(self, host: str, ping_count = 10):
        return await (
            await asyncio.create_subprocess_exec('ping', '-c', f'{ping_count}', host, stdout=subprocess.DEVNULL)
        ).wait() == 0

    async def ping_loop(self):
        await asyncio.sleep(60 * 10)
        while True:
            await asyncio.sleep(60 * 1)
            try:
                if await self.ping_some(str(self.full_unit_config.modem.ping_address), 5):
                    self.technical_status.statuses.modem_no_work_count.value = 0
            except Exception:
                pass

    async def fix_reboot_on_invalid_modem_loop(self):
        async def try_reboot(is_modem_time: bool = True, is_invalid_usb = False):
            if not await self.ping_some(str(self.full_unit_config.modem.ping_address), ping_count=60):
                if is_modem_time:
                    self.update_invalid_modem_time()
                else:
                    self.update_invalid_sim_time()

                if is_invalid_usb:
                    self.raise_info('ModemMalfunctionRebootUsb', False, True)
                else:
                    self.raise_info('ModemMalfunctionReboot', False, True)

                await self.reboot_power(False)

        await asyncio.sleep(60 * 15.3)
        while True:
            await asyncio.sleep(9.5 * 60)
            await asyncio.sleep(15)
            try:
                if not self.full_unit_config.alarm_config.modem_alarm:
                    continue

                tz = timezone(timedelta(hours=self.full_unit_config.unit_configuration.timezone))
                tm = datetime.now(tz)
                if tm.hour == 2 and tm.minute < 11:
                    modem_demon = self.get_modem_demon()
                    if modem_demon is None or self.technical_status.statuses.modem_no_work_count.value > 360:
                        await try_reboot(is_invalid_usb=modem_demon is None)
                    elif self.technical_status.statuses.modem_no_sim.value > 16:
                        await try_reboot(False)

                # if self.is_reboot_available_by_invalid_modem() and (self.get_modem_demon() is None or self.technical_status.statuses.modem_no_work_count.value > 360):
                #     await try_reboot()
                #
                # if self.is_reboot_available_by_invalid_sim() and (self.technical_status.statuses.modem_no_sim_count.value > 16):
                #     await try_reboot(False)

            except Exception:
                pass

    @staticmethod
    def load_full_config() -> FullUnitConfiguration:
        default = FullUnitConfiguration(
            unit_configuration=UnitConfiguration(),
            relay_channels=[RelayChannel(index=idx) for idx in range(settings.SERVICES_VIRTUAL_RELAYS_NUMBER)],
            service_channels=[ServiceChannel(index=idx) for idx in range(settings.SERVICES_MODULE_NUMBER)],
            alarm_config=AlarmConfig(),
            modem=Modem()
        )

        uuid = load_const('block_uuid', str, str(uuid4()))
        save_const('block_uuid', uuid)
        save_config('plb/uuid', uuid)

        config: FullUnitConfiguration = load_config(
            'si/full_unit_config',
            FullUnitConfiguration,
            load_const(
                'full_unit_config',
                FullUnitConfiguration,
                default
            )
        )

        config.unit_configuration.version_info = UnitConfiguration().version_info
        config.unit_configuration.uuid = uuid
        try:
            unit_type = load_const('block_type', str).rstrip('\n')
            config.unit_configuration.unit_type = unit_type
        except Exception as e:
            #print(f'load_full_config: {e}')
            pass

        return config

    def load_alarms_description(self) -> list[AlarmDescription]:
        return [info.alarm_code_description for info in ALARMS]

    # async def check_usbhub_loop(self):
    #     await asyncio.sleep(60 * 15.3)
    #
    #     while True:
    #         demon_name = self.get_modem_demon()
    #
    #         for i in range(3):
    #             if demon_name is not None:
    #                 break
    #             await asyncio.sleep(30)
    #             demon_name = self.get_modem_demon()
    #         else:
    #             if self.full_unit_config.alarm_config.modem_alarm:
    #                 await self.reboot_power()
    #
    #         await asyncio.sleep(60 * 9.7)


    async def ping_server_loop(self):
        await self.reset_server_current_alarms()
        while True:
            await asyncio.sleep(7)
            prev_server_status = self.server_status
            # self._server_logger.debug(f'[PREV SERVER STATUS] -> Status: {prev_server_status}')
            try:
                uri = f'http://{self.get_server_address()}/server_connection/ping/'
                async with aiohttp.request('GET', uri) as response:
                    # self._server_logger.debug(f'[PING] -> Status: {response.status}')
                    if response.status == 200:
                        self.server_status = True
                    else:
                        self.server_status = False

            except Exception as e:
                # self._server_logger.debug(f'[PING] -> Error: {e}')
                self.server_status = False
                # print("ping_server_loop: {e}")

            if self.server_status:
                await self.sync_time_with_server()


            if not prev_server_status and self.server_status:
                rsca_result = await self.reset_server_current_alarms()
                # self._server_logger.debug(f'[REST CURRENT] -> Result: {rsca_result}')

            if self.server_status:
                # self._server_logger.debug(f'[SEND UNSENT] -> UnsentCount: {len(self.unsent_alarms)}')
                sua_result = await self.send_unsent_alarms()
                # self._server_logger.debug(f'[SEND UNSENT] -> Result: {sua_result}, UnsentCount: {len(self.unsent_alarms)}')

    async def collect_telemetry(self):
        def cast(f: BaseModel, t: Type, transmutations: dict):
            fields = f.dict()
            out = t()
            for key in fields.keys():
                transmutator = transmutations.get(key, lambda x: x)
                value = transmutator(fields[key])
                try:
                    getattr(out, key).value = value
                except:
                    pass

            return out

        async def check_modem():
            try:
                modem_demon = self.get_modem_demon()
                log = subprocess.run(['systemctl', 'status', modem_demon], stdout=subprocess.PIPE)
                lines = log.stdout.split(b'\n')
                lines = list(filter(lambda x: len(x) > 8 and x.find(b'pppd') < 0, lines))
                status = lines[-1].split(b' ')
                status1 = lines[-2].split(b' ')
                if modem_demon == ModemConfig.MODEM_DEMON_WDIALIRZ:
                    modem_status = \
                        (status1[-2] == b'Connect' and status1[-1] == b'OK') or \
                        (status[-2] == b'Connect' and status[-1] == b'OK')
                else:
                    modem_status = \
                        (status[-2] == b'Connect' and status[-1] == b'OK')

                if not modem_status:
                    for line in reversed(lines):
                        status = line.split(b' ')
                        if status[-2] == b'Connect' and status[-1] == b'OK':
                            return ModemState.SomethingWrong

                        if status[-2] == b'No' and status[-1] == b'SIM':
                            return ModemState.NoSim

                # if modem_status:
                #     self.set_modem_rebooted(False)

                return ModemState.ConnectionOk if modem_status else ModemState.SomethingWrong
            except Exception:
                return ModemState.SomethingWrong

        from services.server_integration.schemas.time_type import TimeType

        control_api = ControlApi()
        rm_api = RelaysModuleApi()
        sm_api = ServicesModuleApi()
        telemetry_api = TelemetryApi()
        ac_api = AutomaticControlApi()
        ocb_api = OcbApi()
        ad_api = Ad936xApi()

        block_state = await control_api.get_control_state('block_state')

        technical_status = TechnicalStatus()

        pilot_telemetry = await ac_api.get_pilot_telemetry()

        rm_configs = await ad_api.get_config(0)
        rm_configs_gains = [rm_configs.gain_0, rm_configs.gain_1]
        rm_configs_attenuation = [int(rm_configs.attenuation_0), int(rm_configs.attenuation_1)]
        technical_status.rm_telemetry = [
            cast(
                await rm_api.get_telemetry(i), TSRmTelemetry,
                {
                    'rx_synth_lock': lambda x: x == 'LOCK',
                    'tx_synth_lock': lambda x: x == 'LOCK'
                }
            )
            for i in range(settings.SERVICES_RELAYS_NUMBER)
        ]
        for i in range(settings.SERVICES_RELAYS_NUMBER):
            if self.full_unit_config.relay_channels[1].rtr_common.rt_rxs_enabled[i] or\
               self.full_unit_config.relay_channels[1].rtr_common.optic_enabled[i]:
                technical_status.rm_telemetry[i].rtr_normal_vswr.value = self.rtr_normal_vswr[i]

                technical_status.rm_telemetry[i].pilots_power_tx_link_status.value =\
                    pilot_telemetry.pilots_power_tx_link_status[i]

                technical_status.rm_telemetry[i].pilots_power_rx_link_status.value = \
                    pilot_telemetry.pilots_power_rx_link_status[i]

                if pilot_telemetry.pilots_power_rx_link_status[i]:
                    technical_status.rm_telemetry[i].pilots_power_rx.value =\
                        pilot_telemetry.pilots_power_rx[i]
                else:
                    technical_status.rm_telemetry[i].pilots_power_rx.value = 0.0

                if pilot_telemetry.pilots_power_tx_link_status[i]:
                    technical_status.rm_telemetry[i].pilots_power_tx.value =\
                        pilot_telemetry.pilots_power_tx[i]
                else:
                    technical_status.rm_telemetry[i].pilots_power_tx.value = 0.0

                technical_status.rm_telemetry[i].up_link_power.value = rm_configs_gains[i]
                technical_status.rm_telemetry[i].down_link_power.value = 70 - rm_configs_attenuation[i]
            else:
                technical_status.rm_telemetry[i].pilots_power_rx.value = 0
                technical_status.rm_telemetry[i].up_link_power.value = 0
                technical_status.rm_telemetry[i].down_link_power.value = 0

        sm_configs = await ad_api.get_config(1), await ad_api.get_config(2)
        sm_configs_gains = [sm_configs[0].gain_0, sm_configs[0].gain_1, sm_configs[1].gain_0, sm_configs[1].gain_1]
        sm_configs_attenuation = [
            int(sm_configs[0].attenuation_0), int(sm_configs[0].attenuation_1),
            int(sm_configs[1].attenuation_0), int(sm_configs[1].attenuation_1)
        ]
        technical_status.sm_telemetry = [
            cast((await sm_api.get_telemetry(i)), TSSmTelemetry, {})
            for i in range(settings.SERVICES_MODULE_NUMBER)
        ]
        for i in range(settings.SERVICES_MODULE_NUMBER):
            main_index = [0, 0, 1, 1][i]
            sub_index = [1, 0, 1, 0][i]
            index = main_index * 2 + sub_index
            technical_status.sm_telemetry[i].ocb_in_work.value = (await ocb_api.get_ocb_in_work_state())[i+2]

            if block_state:
                technical_status.sm_telemetry[i].temperature.is_need = False
                technical_status.sm_telemetry[i].voltage_28v.is_need = False
                technical_status.sm_telemetry[i].current_28v.is_need = False
                technical_status.sm_telemetry[i].lna_5v.is_need = False
                technical_status.sm_telemetry[i].sm_output_power.is_need = False
                technical_status.sm_telemetry[i].pap.is_need = False
            else:
                technical_status.sm_telemetry[i].base_station_current_power.is_need = False
                technical_status.sm_telemetry[i].pap.value = pilot_telemetry.sm_paps[i]

            self.full_unit_config.service_channels[i].type = await sm_api.get_module_type(i)

            if self.full_unit_config.service_channels[i].str.enabled:
                technical_status.sm_telemetry[i].base_station_current_power.value =\
                    pilot_telemetry.base_station_current_power[i]
                technical_status.sm_telemetry[i].sm_output_power.value =\
                    pilot_telemetry.sm_output_power[i]
                if block_state:
                    technical_status.sm_telemetry[i].up_link_power.value = 70 - sm_configs_attenuation[index]
                    technical_status.sm_telemetry[i].down_link_power.value = sm_configs_gains[index]
                else:
                    technical_status.sm_telemetry[i].down_link_power.value = 70 - sm_configs_attenuation[index]
                    technical_status.sm_telemetry[i].up_link_power.value = sm_configs_gains[index]
            else:
                technical_status.sm_telemetry[i].base_station_current_power.value = 0
                technical_status.sm_telemetry[i].sm_output_power.value = 0
                technical_status.sm_telemetry[i].up_link_power.value = 0
                technical_status.sm_telemetry[i].down_link_power.value = 0

            if block_state:
                technical_status.sm_telemetry[i].base_station_current_power.is_need = True
            else:
                technical_status.sm_telemetry[i].lna_5v.is_need = True
                technical_status.sm_telemetry[i].sm_output_power.is_need = True
                technical_status.sm_telemetry[i].voltage_28v.is_need = True

        technical_status.sfp_telemetry = [
            cast(module, TSSfpModule, {
                'rx_los': lambda x: not x,
                'tx_fault': lambda x: not x,
                'mod_abs': lambda x: not x
            })
            for module in (await telemetry_api.get_sfp_telemetry()).modules
        ]
        for sfp in technical_status.sfp_telemetry:
            if not sfp.mod_abs.value:
                sfp.rx_power_dbm.value = '-'
                sfp.tx_power_watt.value = '-'
            elif not sfp.rx_los.value:
                sfp.rx_power_dbm.value = '-'
            elif not sfp.tx_fault.value:
                sfp.tx_power_watt.value = '-'

        technical_status.gps_telemetry = cast(
            await telemetry_api.get_gps_telemetry(), TSGpsTelemetry,
            {
                'timestamp': lambda x: x.timestamp(),
                'last_capture_time': lambda x: False if x is None else x.timestamp(),
                'last_bs_capture_time': lambda x: False if x is None else x.timestamp(),
            }
        )
        technical_status.gps_telemetry.reference_freq_40mhz.value = await ac_api.get_actual_reference_freq_40mhz()

        if technical_status.gps_telemetry.status.value:
            if not self.server_time_sync or not self.hands_time_sync:
                self.alarm_db.update_current_time_point()
                self.gps_time_sync = True
                save_config("block_time/time_type", str(TimeType.GPS))

        technical_status.gps_telemetry.time_type.value = load_config("block_time/time_type", str, str(TimeType.NONE))
        technical_status.statuses.server.value = self.server_status


        if self._check_mode_counter == 0:
            modem_status = await check_modem()
            modem_no_work = True if modem_status == ModemState.SomethingWrong else False
            modem_no_sim = True if modem_status == ModemState.NoSim else False
        else:
            modem_no_work = self.technical_status.statuses.modem_no_work.value
            modem_no_sim = self.technical_status.statuses.modem_no_sim.value

        technical_status.statuses.modem_no_work.value = modem_no_work
        technical_status.statuses.modem_no_sim.value = modem_no_sim

        if modem_no_work:
            technical_status.statuses.modem_no_work_count.value += 1
        else:
            technical_status.statuses.modem_no_work_count.value = 0

        if modem_no_sim:
            technical_status.statuses.modem_no_sim_count.value += 1
        else:
            technical_status.statuses.modem_no_sim_count.value = 0

        self._check_mode_counter += 1
        if self._check_mode_counter > 22:
            self._check_mode_counter = 0

        for idx, status in enumerate(await ad_api.get_statuses()):
            if idx == 3:
                getattr(technical_status.statuses, f'ad9364_4').value = status
            else:
                getattr(technical_status.statuses, f'ad9361_{idx+1}').value = status

        ocs_state = await ocb_api.get_ocb_in_work_state()
        for index, attr in zip(range(7), ['link_0_ocb', 'link_1_ocb', 'service_0_ocb', 'service_1_ocb', 'service_2_ocb', 'service_3_ocb', 'service_5v_ocb']):
            getattr(technical_status.ocb_states, attr).value = ocs_state[index]

        technical_status.gps_telemetry.bs_sync.value = pilot_telemetry.pss_found
        technical_status.dsp_telemetry = cast(await telemetry_api.get_dsp_telemetry(), TSDspTelemetry, {})
        technical_status.pwr_telemetry = cast(await telemetry_api.get_pwr_telemetry(), TSPwrTelemetry, {})
        technical_status.ocb_states = cast(await ocb_api.get_all_ocb_states(), TSOcbStates, {})
        technical_status.sensors[0].value = await control_api.get_control_state('external_sensor_1')
        technical_status.sensors[1].value = await control_api.get_control_state('external_sensor_2')

        try:
            with open('/home/debian/rcs/environment/dma/current_28v', "rt") as current_28v:
                data = current_28v.read().split('\n')
                technical_status.rm_telemetry[0].current_rm_module.value = float(data[0].strip())
                technical_status.rm_telemetry[1].current_rm_module.value = float(data[1].strip())
                technical_status.sm_telemetry[0].current_28v.value = float(data[2].strip())
                technical_status.sm_telemetry[1].current_28v.value = float(data[3].strip())
                technical_status.sm_telemetry[2].current_28v.value = float(data[4].strip())
                technical_status.sm_telemetry[3].current_28v.value = float(data[5].strip())

            for ad_index, ts_field in enumerate(['ad9361_1_invalid', 'ad9361_2_invalid', 'ad9361_3_invalid', 'ad9364_invalid']):
                status = load_config(f'dma/ad{ad_index}_eq_invalid', bool, False)
                getattr(technical_status.statuses, ts_field).value = status
        except:
            pass

        self.technical_status = technical_status

    def raise_info(self, name: str, value: float | int | bool | str = 0.0, end_value: float | int | bool | str = None):
        code = ALARM_CODE_BY_NAME[name]
        info = ALARMS[code]
        if end_value is None:
            end_value = value

        if code == 237:
            original_value = value
            original_end_value = end_value

            value = 0
            end_value = 1

        alarm = Alarm(
            code=code,
            state=True,
            value=value,
            name=info.alarm_code_description.name,
            group=info.alarm_code_description.group,
            subgroup=info.alarm_code_description.subgroup,
            time=int(datetime.now().timestamp()),
            time_offset=int(time.monotonic()),
            time_point=CURRENT_TIME_POINT[0],
            serial_number=self.full_unit_config.unit_configuration.main_module.serial_number,
            device_name=self.full_unit_config.unit_configuration.block_name,
            user=info.for_all_users,
            server=info.for_all_users
        )
        self.update_alarm(info, alarm)

        alarm = alarm.copy(update={
            'state': False,
            'end_value': end_value,
            'time': int(datetime.now().timestamp()),
            'time_offset': int(time.monotonic()),
            'time_point': CURRENT_TIME_POINT[0],
            'end_time': int(datetime.now().timestamp()),
            'end_time_offset': int(time.monotonic()),
            'end_time_point': CURRENT_TIME_POINT[0]
        })
        aid: int = self.update_alarm(info, alarm)

        if code == 237:
            self.block_version_db.add_alarm(aid, original_value, original_end_value)

    def raise_alarm(self, name: str, state: bool = True, value: float | int | bool = 0.0):
        code = ALARM_CODE_BY_NAME[name]
        info = ALARMS[code]
        if not self.alarms_context.check_time(info) and state:
            return

        if not self.alarms_context.press_actuation_count(info, state):
            return

        alarm = Alarm(
            code=code,
            state=state,
            value=value,
            name=info.alarm_code_description.name,
            group=info.alarm_code_description.group,
            subgroup=info.alarm_code_description.subgroup,
            time=int(datetime.now().timestamp()),
            time_offset=int(time.monotonic()),
            time_point=CURRENT_TIME_POINT[0],
            serial_number=self.full_unit_config.unit_configuration.main_module.serial_number,
            device_name=self.full_unit_config.unit_configuration.block_name,
            user=self.alarms_context.press_actuation_count(info, state, role='user') and info.for_all_users,
            server=self.alarms_context.press_actuation_count(info, state, role='server') and info.for_all_users
        )
        self.update_alarm(info, alarm)
        if name == 'PowerFailure':
            save_config('si/current_alarms', self.current_alarms)

    def update_alarm(self, info: AlarmInfo, alarm: Optional[Alarm]):
        code = info.alarm_code_description.code
        if alarm is None:
            # if self.current_alarms[code] is not None:
            #     asyncio.create_task(
            #         self.send_alarm_to_server(
            #             self.current_alarms[code].copy(update={'state': False}),
            #             'set-current-alarm'
            #         )
            #     )
            #     self.current_alarms[code] = None

            return

        if code in self._blocked_alarms:
            alarm.server = False
            alarm.user = False

        cur_alarms = self.current_alarms[code]
        match (cur_alarms is not None, alarm.state):
            case(False, False):
                pass
            case (True, True):
                cur_alarms.value = alarm.value
                if cur_alarms.user != alarm.user:
                    cur_alarms.user = alarm.user
                    self.alarm_db.update_role_state(cur_alarms.aid, alarm.user, alarm.server)

                if cur_alarms.server != alarm.server:
                    if alarm.server and not cur_alarms.server:
                        self.log_alarm_to_server(cur_alarms.copy(update={
                            'start_value': alarm.value,
                            'value': alarm.value,
                            'time': alarm.time,
                            'time_offset': alarm.time_offset,
                            'time_point': alarm.time_point
                        }))

                    cur_alarms.server = alarm.server
                    self.alarm_db.update_role_state(cur_alarms.aid, alarm.user, alarm.server)

            case (True, False):
                alarm = alarm.copy(update={
                    'aid': cur_alarms.aid,
                    'time': cur_alarms.time,
                    'time_offset': cur_alarms.time_offset,
                    'time_point': cur_alarms.time_point,
                    'end_time': int(datetime.now().timestamp()),
                    'end_time_offset': int(time.monotonic()),
                    'end_time_point': CURRENT_TIME_POINT[0],
                    'start_value': cur_alarms.start_value,
                    'value': cur_alarms.start_value,
                    'end_value': alarm.value,
                    'user': cur_alarms.user,
                    'server': cur_alarms.server
                })
                self.log_alarm(alarm)
                self.current_alarms[code] = None
                return cur_alarms.aid

            case (False, True):
                aid = self.log_alarm(alarm.copy())
                self.current_alarms[code] = alarm.copy(update={'aid': aid, 'start_value': alarm.value})

    def collect_alarms(self):
        self.alarms_context.update(self.full_unit_config, self.technical_status, )
        for alarm_info in ALARMS:
            alarm = self.alarms_context.check(
                alarm_info,
                self.current_alarms[alarm_info.alarm_code_description.code]
            )
            self.update_alarm(alarm_info, alarm)

        save_config('si/current_alarms', self.current_alarms)

    def log_alarm_to_server(self, alarm: Alarm):
        asyncio.create_task(self.send_alarm_to_server(alarm, 'set-current-alarm'))
        asyncio.create_task(self.send_alarm_to_server(alarm, 'set-log-alarm'))

    def log_alarm(self, alarm: Alarm):
        aid = self.alarm_db.log_alarms(alarm)
        if alarm.server:
            self.log_alarm_to_server(alarm)

        return aid

    async def alarm_telemetry_loop(self):
        await asyncio.sleep(20)

        while True:
            try:
                if self.telemetry_lock is None:
                    await self.collect_telemetry()
                    self.collect_alarms()
                else:
                    prev = self.telemetry_lock
                    delta = (datetime.now() - prev).total_seconds()
                    await asyncio.sleep(clamp(10, 5.0, 11.0-delta))
                    if self.telemetry_lock == prev:
                        self.telemetry_lock = None

            except Exception as e:
                #print(f"alarm_telemetry_loop: {e}")
                pass

            await asyncio.sleep(delay=4.0)

    def get_server_address(self):
        server_info = self.full_unit_config.unit_configuration.server
        if server_info.port < 100:
            server_address = f'{server_info.ip}'
        else:
            server_address = f'{server_info.ip}:{server_info.port}'

        return server_address

    def clear_alarms(self):
        for cur_alarm in filter(lambda x: x is not None, self.current_alarms):
            info = ALARMS[cur_alarm.code]
            self.update_alarm(
                info,
                cur_alarm.copy(
                    update={
                        'value': -0.0,
                        'end_value': -0.0,
                        'state': False,
                    }
                )
            )

        self.alarm_db.clear()

    async def reset_server_current_alarms(self):
        if True not in [self.server_time_sync, self.hands_time_sync]:
            return False

        result = False
        async with self._c_lock:
            server_address = self.get_server_address()
            try:
                sim_ip = self.full_unit_config.modem.sim_ip
                current_alarms = [
                    ServerAlarm.from_alarm(
                        alarm,
                        sim_ip,
                        self.full_unit_config.unit_configuration.uuid,
                        self.alarm_db
                    ).dict()
                    for alarm in filter(lambda x: x is not None and x.server, self.current_alarms)
                ]

                uri = f'http://{server_address}/alarms/set-not-send-alarms/'
                async with aiohttp.request(
                        'POST', uri,
                        json=current_alarms
                ) as response:
                    if response.status == 200:
                        result = True

            except Exception as e:
                return False

        return result

    async def sync_time_with_server(self):
        from services.server_integration.schemas.time_type import TimeType

        if self._is_time_synchronized:
            gps_set_time_time = load_config('tm/gps_set_time_time', float, 0.0)

            days14_offset =  60 * 60 * 24 * 14
            current_time = datetime.now().timestamp()

            if (0 if self._server_time_sync_time is None else self._server_time_sync_time) + days14_offset > current_time:
                return

            if gps_set_time_time != 0.0 and gps_set_time_time + days14_offset > current_time:
                return

        try:
            server_address = self.get_server_address()
            uri = f'http://{server_address}/server_information/get-time/'
            async with aiohttp.request('GET', uri) as response:
                if response.status == 200:
                    current_time = datetime.fromtimestamp(float(await response.text())) + timedelta(seconds=4)
                    subprocess.call(
                        ['date', '-s', current_time.strftime('%Y-%m-%d %H:%M:%S')],
                        stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT
                    )
                    self.alarm_db.update_current_time_point()
                    self.server_time_sync = True
                    save_config("block_time/time_type", str(TimeType.SERVER))
                    self._server_time_sync_time = datetime.now().timestamp()
        except Exception as e:
            pass

    async def send_unsent_alarms(self, max_count=100):
        if True not in [self.server_time_sync, self.hands_time_sync]:
            return False

        for _ in range(max_count):
            if len(self.unsent_alarms2) == 0:
                break

            alarm = self.pop_unsent_alarm()
            if alarm is None:
                continue

            if not await self.send_alarm_to_server(alarm, 'set-log-alarm'):
                save_config('si/unsent_alarms2', self.unsent_alarms2)
                return False

        save_config('si/unsent_alarms2', self.unsent_alarms2)
        return True

    def pop_unsent_alarm(self) -> Alarm | None:
        try:
            (aid, alarm) = self.unsent_alarms2.popitem()
            if alarm[0] is not None:
                if alarm[1] is not None:
                    self.unsent_alarms2[aid] = (None, alarm[1])

                return alarm[0]
            elif alarm[1] is not None:
                return alarm[1]
            else:
                return None

        except Exception:
            return None

    def append_unsent_alarm(self, alarm: Alarm):
        cur =  self.unsent_alarms2.get(alarm.aid, (None, None))
        if alarm.state:
            self.unsent_alarms2[alarm.aid] = (alarm, cur[1])
        else:
            self.unsent_alarms2[alarm.aid] = (cur[0], alarm)

        save_config('si/unsent_alarms2', self.unsent_alarms2)

    async def send_alarm_to_server(self, alarm: Alarm, endpoint: Literal['set-log-alarm', 'set-current-alarm']):
        async def send(alarm: Alarm, endpoint: Literal['set-log-alarm', 'set-current-alarm']):
            _alarm = alarm

            def add_unsent_alarm():
                if endpoint == 'set-log-alarm':
                    self.append_unsent_alarm(_alarm)

            if not self.server_status:
                add_unsent_alarm()
                return False

            server_address = self.get_server_address()
            try:
                sim_ip = self.full_unit_config.modem.sim_ip
                alarm = ServerAlarm.from_alarm(
                    alarm,
                    sim_ip,
                    self.full_unit_config.unit_configuration.uuid,
                    self.alarm_db
                )
                if self.server_time_sync or self.hands_time_sync:
                    uri = f'http://{server_address}/alarms/{endpoint}/'
                    async with aiohttp.request(
                        'POST', uri,
                        json=alarm.dict()
                    ) as response:
                        if response.status == 200:
                            return True
                        else:
                            self.server_status = False
                            add_unsent_alarm()
                else:
                    add_unsent_alarm()

            except Exception as e:
                self.server_status = False
                add_unsent_alarm()

            return False

        if endpoint == 'set-current-alarm':
            async with self._c_lock:
                return await send(alarm, endpoint)
        else:
            return await send(alarm, endpoint)

    def get_current_alarms_filtered(self, request: AlarmsRequest, role: Literal['adjuster', 'user', 'server'] = 'adjuster') -> AlarmResponseConfiguration:
        block_state = self.full_unit_config.unit_configuration.unit_type == 'FSC'
        descriptions = self.load_alarms_description()

        def get_field(alarm: Alarm, key: str):
            if key in ['code', 'value', 'state', 'time', 'aid']:
                return getattr(alarm, key)
            else:
                value = getattr(descriptions[alarm.code], key)

                if value == 'SM':
                    return 'BS' if block_state else 'SM'

                return value

        total_alarm_count = [0]
        total_warning_count = [0]
        alarms_count_by_group = {
            'Minor': [0, 0], 'Critical': [0, 0], 'DSP': [0, 0], 'Modem': [0, 0], 'Optic': [0, 0], 'Power': [0, 0], 'RM': [0, 0],
            'SFP': [0, 0], 'SM': [0, 0], 'Sens': [0, 0], 'GPS': [0, 0], 'AC': [0, 0], 'BS': [0, 0],
            'Info': [0, 0]
        }

        warning_filter = None
        for f in request.filters:
            if f.key == 'is_warning':
                warning_filter = f.value

        request.filters = list(filter(lambda fi: fi.key != 'is_warning', request.filters))


        time_point_cache = {}
        def _filter(alarm: Optional[Alarm]):
            if alarm is None:
                return False

            if alarm.time_point is not None:
                point_timestamp = time_point_cache.get(alarm.time_point, self.alarm_db.get_time_point_timestamp(alarm.time_point))
                time_point_cache[alarm.time_point] = point_timestamp
                if point_timestamp is not None:
                    alarm.time = point_timestamp + alarm.time_offset

            if alarm.end_time_point is not None:
                end_point_timestamp = time_point_cache.get(alarm.end_time_point, self.alarm_db.get_time_point_timestamp(alarm.end_time_point))
                time_point_cache[alarm.end_time_point] = end_point_timestamp
                if end_point_timestamp is not None:
                    alarm.end_time = end_point_timestamp + alarm.end_time_offset

            if not ({'adjuster': True, 'user': alarm.user, 'server': alarm.server}[role]):
                return False

            if not(request.start_date <= datetime.fromtimestamp(alarm.time) <= request.end_date):
                return False

            if alarm.group == 'SM' and block_state:
                alarm.group = 'BS'

            if descriptions[alarm.code].is_warning:
                total_warning_count[0] += 1
                alarms_count_by_group[alarm.group][0] += 1
                alarms_count_by_group['Minor'][0] += 1
            else:
                total_alarm_count[0] += 1
                alarms_count_by_group[alarm.group][1] += 1
                alarms_count_by_group['Critical'][1] += 1

            if warning_filter is not None and get_field(alarm, 'is_warning') != warning_filter:
                return False

            for filter in request.filters:
                if get_field(alarm, filter.key) == filter.value:
                    break
            else:
                return len(request.filters) == 0

            return True

        filtered_alarms = list(filter(_filter, self.current_alarms))
        filtered_alarms.sort(key=lambda x: get_field(x, request.sort_by), reverse=request.order == 'desc')
        filtered_alarms = filtered_alarms[request.offset: request.offset + request.count]
        alarm: Alarm
        for alarm in filtered_alarms:
            description: AlarmDescription = descriptions[alarm.code]
            alarm.alarm_type = BlockAlarmType.WARNING if description.is_warning else BlockAlarmType.ERROR
            alarm.serial_number = self.full_unit_config.unit_configuration.main_module.serial_number
            alarm.device_name = self.full_unit_config.unit_configuration.block_name
            if request.language == 'en':
                alarm.description = description.description_en
            else:
                alarm.description = description.description_ru

        return AlarmResponseConfiguration(
            total_warnings_count=total_warning_count[0],
            total_alarms_count=total_alarm_count[0],
            alarms_group_count=[
                AlarmsGroupCount(
                    block_name=block_name, warning_count=count[0], alarm_count=count[1]
                ) for block_name, count in
                alarms_count_by_group.items()
            ],
            alarms=filtered_alarms
        )

    def get_alarms_log_filtered(self, request: AlarmsRequest, role: Literal['adjuster', 'user', 'server'] = 'adjuster') -> AlarmResponseConfiguration:
        block_state = self.full_unit_config.unit_configuration.unit_type == 'FSC'
        cast_key = lambda key: f'[{key}]'

        def cast_value(value):
            if block_state and isinstance(value, str):
                value = value.replace('BS', "SM")

            return {
                str: lambda x: f'"{value}"',
                int: lambda x: x,
                bool: lambda x: x,
                float: lambda x: x,
                datetime: lambda x: x.timestamp(),
            }[type(value)](value)

        warning_filter = None
        for f in request.filters:
            if f.key == 'is_warning':
                warning_filter = f.value

        request.filters = list(filter(lambda fi: fi.key != 'is_warning', request.filters))
        if warning_filter is None:
            _filter = f'({request.start_date.timestamp()} <= [time] and [time] <= {request.end_date.timestamp()})'
        else:
            _filter = f'({request.start_date.timestamp()} <= [time] and [time] <= {request.end_date.timestamp()} and [is_warning] = {int(warning_filter)})'

        if len(request.filters) > 0:
            _filter += ' and '
            _filter += '({})'.format(
                ' or '.join([f'({cast_key(f.key)} = {cast_value(f.value)})' for f in request.filters]))

        order_by = f'{cast_key(request.sort_by)} {request.order}'

        description_language = request.language

        alarm_response = AlarmResponseConfiguration(
            total_warnings_count=self.alarm_db.get_alarms_count(_filter + ' and ([is_warning] = 1)', role),
            total_alarms_count=self.alarm_db.get_alarms_count(_filter + ' and ([is_warning] = 0)', role),
            alarms_group_count=[
                AlarmsGroupCount(
                    block_name='BS' if block_state and group == 'SM' else group, warning_count=count[0], alarm_count=count[1]
                ) for group, count in
                #self.alarm_db.get_log_alarms_count_by_group(filter, request)
                self.alarm_db.get_alarms_count_by_group(_filter, request, role).items()
            ],
            alarms=[self.convert_update_version_alarm(alarm, block_state) if alarm[0] == 237 else
                Alarm(
                    code=alarm[0], aid=alarm[1],
                    start_value=alarm[2], value=alarm[2], end_value=alarm[3],
                    state=alarm[4],
                    time=int(alarm[5]), end_time=None if alarm[6] is None else int(alarm[6]),
                    name=alarm[7], group='BS' if alarm[8] == 'SM' and block_state else alarm[8], subgroup=alarm[9],
                    alarm_type=BlockAlarmType.WARNING if bool(alarm[10]) else BlockAlarmType.ERROR,
                    description=alarm[11],
                    serial_number=self.full_unit_config.unit_configuration.main_module.serial_number,
                    device_name=self.full_unit_config.unit_configuration.block_name,
                ) for alarm in self.alarm_db.get_alarms_filtered(description_language, _filter, order_by,
                                                  request.offset, request.count, role)
            ]
        )

        return alarm_response

    def convert_update_version_alarm(self, alarm, block_state) -> Alarm:
        version_alarm_by_aid = self.block_version_db.get_alarm_by_aid(alarm[1])
        alarm: Alarm = Alarm(
            code=alarm[0], aid=alarm[1],
            start_value=alarm[2], value=version_alarm_by_aid[0], end_value=version_alarm_by_aid[1],
            state=alarm[4],
            time=int(alarm[5]), end_time=None if alarm[6] is None else int(alarm[6]),
            name=alarm[7], group='BS' if alarm[8] == 'SM' and block_state else alarm[8], subgroup=alarm[9],
            alarm_type=BlockAlarmType.WARNING if bool(alarm[10]) else BlockAlarmType.ERROR,
            description=alarm[11],
            serial_number=self.full_unit_config.unit_configuration.main_module.serial_number,
            device_name=self.full_unit_config.unit_configuration.block_name,
        )

        return alarm

    def get_newest_oldest_alarms_date(self, role: Literal['adjuster', 'user', 'server'] = 'adjuster'):
        try:
            dates = self.alarm_db.get_newest_oldest_history_date(role)
            dates_to_list = [date[0] for date in dates]
            if len(dates_to_list) < 3:
                dates_to_list = [0, 0, 0]
        except Exception:
            dates_to_list = [0, 0, 0]

        return OldestNewestAlarmsDate(
            newest_date=datetime.fromtimestamp(dates_to_list[0]),
            oldest_date=datetime.fromtimestamp(dates_to_list[1]),
            alarms_total_count=dates_to_list[2]
        )

    def get_cities_timezones(self, request: TimezonesRequest) -> Timezones:
        return Timezones(
            timezones=[
                CityTimezone(
                    city=timezones[0],
                    timezone=timezones[1]
                ) for timezones in self.timezone_db.get_cities_timezones(request)
            ]
        )

    def get_city_by_timezone(self, request: CityByTimezoneRequest):
        return self.timezone_db.get_city_by_timezone(request)

    @staticmethod
    def set_indexes(config: FullUnitConfiguration) -> FullUnitConfiguration:
        for idx, channel in enumerate(config.relay_channels):
            channel.index = idx

        for idx, service in enumerate(config.service_channels):
            service.index = idx

        return config

    @staticmethod
    def up_convert_values(config: FullUnitConfiguration) -> FullUnitConfiguration:
        multiplier = 1_000_000
        for channel in config.relay_channels:
            for rtr in channel.rtrs:
                rtr.central_frequency *= multiplier

            channel.rtr_common.bandwidth *= multiplier
            channel.rtr_common.frequency_offset *= multiplier

        for service in config.service_channels:
            service.str.carrier_frequency *= multiplier
            service.frequency_offset *= multiplier
            service.bandwidth *= multiplier

        return config

    @staticmethod
    def down_convert_values(config: FullUnitConfiguration) -> FullUnitConfiguration:
        multiplier = 1.0 / 1_000_000.0
        for channel in config.relay_channels:
            for rtr in channel.rtrs:
                rtr.central_frequency *= multiplier

            channel.rtr_common.bandwidth *= multiplier
            channel.rtr_common.frequency_offset *= multiplier

        for service in config.service_channels:
            service.str.carrier_frequency = round(service.str.carrier_frequency * multiplier, 1)
            service.frequency_offset *= multiplier
            service.bandwidth *= multiplier

        return config

    async def set_calibrate_status(self, msg: CalibrateMessage):
        await self._sir_api.set_calibrate_status(msg)

    async def start_calibrate(self) -> bool:
        ad_api = Ad936xApi()
        ac_api = AutomaticControlApi()
        unit_config = self.full_unit_config.unit_configuration
        control_api = ControlApi()

        block_type = await control_api.get_control_state('block_state')

        calibrate_status = await ac_api.calibrate(
            [sm.str.output_power for sm in self.full_unit_config.service_channels],
            [sm.cable_loss.dl for sm in self.full_unit_config.service_channels],
            [sm.str.enabled for sm in self.full_unit_config.service_channels],
            [rm.output_power_link.output_power for rm in self.full_unit_config.relay_channels[0].rtrs],
            [rm.output_power_link.cable_loss for rm in self.full_unit_config.relay_channels[0].rtrs],
            [rm_enabled for rm_enabled in self.full_unit_config.relay_channels[0].rtr_common.rt_rxs_enabled],
            unit_config.acc_base_power_calibrate,  unit_config.acc_link_calibrate, unit_config.acc_tx_service_calibrate
        )

        if self.full_unit_config.unit_configuration.simulation_state:
            await DspApi().set_simulation_of_chain(True)

        clamp_b = partial(clamp, 70, 1)
        if calibrate_status and block_type:
            config_0 = await ad_api.get_config(1)
            config_1 = await ad_api.get_config(2)
            channels = self.full_unit_config.service_channels

            channels[1].str.gain.dl = clamp_b(config_0.gain_0)
            channels[0].str.gain.dl = clamp_b(config_0.gain_1)
            channels[3].str.gain.dl = clamp_b(config_1.gain_0)
            channels[2].str.gain.dl = clamp_b(config_1.gain_1)

        self.full_unit_config.unit_configuration.calibration_state =\
            CalibrationState.CALIBRATED if calibrate_status else CalibrationState.CALIBRATION_FAILED

        self.save_full_config(self.full_unit_config)

        await self.process_full_unit_config(self.full_unit_config, False, self.full_unit_config)

        self.raise_info('BlockCalibrated', True, False)

        return calibrate_status

    async def special_calibrate(self, index: int, power: float):
        ac = AutomaticControlApi()

        powers = [0.0] * 6
        cable_loss = [0.0] * 6
        states = [False] * 6

        powers[index] = power
        states[index] = True
        if index == 1:
            DevMem(address=0x83C1_0000)[25] = True

        status = False
        try:
            status = await ac.calibrate(
                powers[2:6], cable_loss[2:6], states[2:6],
                powers[0:2], cable_loss[0:2], states[0:2],
                2 <= index <= 5, 0 <= index <= 1, 2 <= index <= 5,
                True
            )

        except Exception:
            pass

        async def off_service_simulation():
            await asyncio.sleep(18)
            if index == 1:
                DevMem(address=0x83C1_0000)[25] = False

            mem = DevMem(address=0x83C1_0000)
            for offset in [9, 10, 11, 19, 20]:
                mem[offset] = False

        asyncio.create_task(off_service_simulation())

        return status

    async def set_ac_magic_numbers(self, index: int, correct_number: float, real_power: float, power: float):
        control_api = ControlApi()
        block_type = await control_api.get_control_state('block_state')
        if block_type and 2 <= index <= 5:
            additional_correct_number = power - real_power
        else:
            additional_correct_number = real_power - power

        ac = AutomaticControlApi()
        await ac.set_output_power_correct_number(index, correct_number)
        await ac.set_additional_output_power_correct_number(index, additional_correct_number)

    async def get_ac_magic_numbers(self, index: int) -> ACMagicNumbers:
        ac = AutomaticControlApi()
        correct_number = await ac.get_output_power_correct_number(index)
        additional_correct_number = await ac.get_additional_output_power_correct_number(index)
        real_power = await ac.get_current_output_power(index)

        return ACMagicNumbers(
            correct_number=correct_number,
            additional_correct_number=additional_correct_number,
            real_power=real_power
        )

    async def process_full_unit_config(self, config: FullUnitConfiguration, is_first_entry: bool, prev_cfg: FullUnitConfiguration, block_ac = False):
        self.full_unit_config = config
        block_state = await self._process_block_state()
        ac_api = AutomaticControlApi()
        dsp_api = DspApi()

        (rm0, rm1) = self._switch_fiber_optic(block_state)
        for chanel in self.full_unit_config.relay_channels:
            prev = chanel.rtr_common.rt_rxs_enabled
            chanel.rtr_common.rt_rxs_enabled = [prev[0] and rm0, prev[1] and rm1]
            chanel.rtr_common.optic_enabled = [not rm0, not rm1]

        await ac_api.set_loops_states(False, False, False, False)

        await self._process_relay_channel_configuration(block_state, prev_cfg, is_first_entry)
        await self._process_service_channels_configuration(block_state, prev_cfg, is_first_entry)
        self._process_modem_configuration(prev_cfg)
        self._process_network_configuration()
        Thread(target=self.update_ip_address).start()
        self._process_fastapi_port(prev_cfg)
        asyncio.create_task(self._process_server(prev_cfg))

        await ac_api.set_adjustment_state(self.full_unit_config.unit_configuration.ustirovka_state)

        await dsp_api.set_simulation_of_chain(self.full_unit_config.unit_configuration.simulation_state)
        if self.full_unit_config.unit_configuration.simulation_state:
            if self._off_simulation_task is not None:
                self._off_simulation_task.cancel()
                self._off_simulation_task = None
            self._off_simulation_task = asyncio.create_task(self.off_simulation_task())

        unit_config = self.full_unit_config.unit_configuration
        await ac_api.set_loops_states(
            unit_config.ac_link_tx,
            unit_config.ac_link_rx,
            unit_config.ac_service_tx,
            unit_config.acc_base_power_calibrate
        )

        # reset_server_current_alarms

        await ac_api.set_sm_base_power(
            [sm.str.output_power for sm in self.full_unit_config.service_channels]
        )
        await ac_api.set_rm_base_power(
            [rm.output_power_link.output_power for rm in self.full_unit_config.relay_channels[0].rtrs]
        )

        cur_sm_base_power = [sm.str.output_power for sm in self.full_unit_config.service_channels]
        prev_sm_base_power = [sm.str.output_power for sm in prev_cfg.service_channels]

        cur_str_enable = [sm.str.enabled for sm in self.full_unit_config.service_channels]
        prev_str_enable = [sm.str.enabled for sm in prev_cfg.service_channels]

        need_ac = any(map(lambda c, p: c != p, cur_sm_base_power, prev_sm_base_power)) or \
            any(map(lambda c, p: c and not p, cur_str_enable, prev_str_enable)) or \
            (self.full_unit_config.unit_configuration.ac_service_tx and not prev_cfg.unit_configuration.ac_service_tx)

        if need_ac and not block_ac:
            await ac_api.run_service_tx_ac()

    async def _process_server(self, prev_cfg: FullUnitConfiguration):
        if prev_cfg.unit_configuration.server.ip != self.full_unit_config.unit_configuration.server.ip or\
                prev_cfg.unit_configuration.server.port != self.full_unit_config.unit_configuration.server.port:
            await self.reset_server_current_alarms()


    def update_ip_address_sync_loop(self):
        time.sleep(15)

        while True:
            time.sleep(66.6)
            try:
                self.update_ip_address()
            except:
                pass

    def update_ip_address(self):
        try:
            demon_name = self.get_modem_demon()

            if demon_name == ModemConfig.MODEM_DEMON_WTELIT:
                interface = ModemConfig.MODEM_INTERFACE_WWX
            elif demon_name == ModemConfig.MODEM_DEMON_WDIALIRZ:
                interface = ModemConfig.MODEM_INTERFACE_PPP
            elif demon_name == ModemConfig.MODEM_DEMON_RNDISIRZ:
                interface = ModemConfig.MODEM_INTERFACE_USB0
            else:
                interface = None

            demon_status = subprocess.getoutput(f'systemctl status {demon_name}.service')
            if 'NO SIM' in demon_status:
                self.full_unit_config.modem.sim_ip = '0.0.0.0'
            else:
                ifconfig = subprocess.getoutput(f'ifconfig {interface}')
                data = ifconfig.splitlines()
                SIM_config = data[1].splitlines()

                SIM_addresses = re.findall(r"\b(?:[0-9]{1,3}\.){3}[0-9]{1,3}\b", SIM_config[0])
                SIM_ip = SIM_addresses[0]
                self.full_unit_config.modem.sim_ip = SIM_ip
        except Exception as e:
            self.full_unit_config.modem.sim_ip = '0.0.0.0'

    def _process_fastapi_port(self, prev_cfg: FullUnitConfiguration):
        if settings.APP_PORT != self.full_unit_config.unit_configuration.device.port and\
            self.full_unit_config.unit_configuration.device.port != prev_cfg.unit_configuration.device.port:
            os.chdir(f'{settings.ROOT_PATH}' + '/software/radius_control_frontend')

            with open("settings.py", "r") as file:
                settings_file_data = file.read().splitlines()

            current_port = settings_file_data[6]
            new_port = current_port[0:29]
            new_port += f'{self.full_unit_config.unit_configuration.device.port}'

            settings_file_data[6] = new_port
            with open("settings.py", "w") as file:
                for line in settings_file_data:
                    file.write(line + '\n')

            subprocess.call(['sh', f'{settings.APP_PATH}/common/third_party/reboot_frontend.sh'])

    def _process_network_configuration(self):
        os.chdir('/home/')
        os.chdir('..')
        os.chdir('/etc/network/')

        with open("interfaces", "r") as file:
            interfaces_file_data = file.read().splitlines()

        current_address = interfaces_file_data[5]
        current_netmask = interfaces_file_data[6]
        current_gateway = interfaces_file_data[7]
        current_mac_adr = interfaces_file_data[9]

        new_address = self.full_unit_config.unit_configuration.device.ip
        new_netmask = self.full_unit_config.unit_configuration.device.netmask
        new_gateway = self.full_unit_config.unit_configuration.device.gateway
        new_mac_adr = self.full_unit_config.unit_configuration.main_module.mac_address

        if current_address != new_address or current_netmask != new_netmask or current_mac_adr != new_mac_adr:
            interfaces_file_data[5] = 'address ' + str(new_address)
            interfaces_file_data[6] = 'netmask ' + str(new_netmask)
            interfaces_file_data[9] = 'hwaddress ether ' + str(new_mac_adr)

            with open("interfaces", "w") as file:
                for line in interfaces_file_data:
                    file.write(line + '\n')

    async def reboot_power(self, user=True):
        self._blocked_alarms.add(108)  # PowerFailure
        if user:
            self.raise_info("UserPowerReboot", True, False)

        if not settings.DEBUG:
            reader, writer = await asyncio.wait_for(serial_asyncio.open_serial_connection(
                loop=asyncio.get_event_loop(),
                url='/dev/ttyUL3', baudrate=38400,
                timeout=1
            ), 3.0)
            writer.write(b'\xAA\xBB\xCC\x03')
            await writer.drain()
            writer.close()

    def reboot_motherboard(self):
        self.raise_info("UserSoftReboot", True, False)
        os.system('sudo reboot')

    def what_demon_is_used(self):
        rndis_daemon_status = subprocess.getoutput('systemctl status rndisirz.service')
        wdial_daemon_status = subprocess.getoutput('systemctl status wdialirz.service')

        # От 25.12.2024 Айрат сказал выключать вдиал, потому что он не работает
        if not self._what_demon_is_used_flag:
            self._what_demon_is_used_flag = True
            subprocess.Popen(['systemctl', 'stop', 'wdialirz.service'])
            subprocess.Popen(['systemctl', 'disable', 'wdialirz.service'])

            if not 'active (running)' in rndis_daemon_status:
                subprocess.Popen(['systemctl', 'enable', 'rndisirz.service'])
                subprocess.Popen(['systemctl', 'start', 'rndisirz.service'])

        return ModemConfig.MODEM_DEMON_RNDISIRZ

    def get_modem_demon(self):
        lsusb = subprocess.getoutput('lsusb')

        if ModemConfig.TELIT_LE910_MODEM_ID in lsusb:
            return ModemConfig.MODEM_DEMON_WTELIT
        elif ModemConfig.SIM_A7602X_H_MODEM_ID in lsusb:
            return self.what_demon_is_used()
        elif ModemConfig.TELIT_910C1_MODEM_ID in lsusb:
            return ModemConfig.MODEM_DEMON_WDIALIRZ
        elif ModemConfig.TELIT_910C4_MODEM_ID in lsusb:
            return ModemConfig.MODEM_DEMON_WDIALIRZ
        elif ModemConfig.UNICOS_UNICOS_8910_MODEM_ID in lsusb:
            return self.what_demon_is_used()
        else:
            return None

    def _process_modem_configuration(self, prev_cfg: FullUnitConfiguration):
        try:
            filenames: list = [ModemConfig.MODEM_DEMON_WTELIT, ModemConfig.MODEM_DEMON_WDIALIRZ, ModemConfig.MODEM_DEMON_RNDISIRZ]

            current_login = prev_cfg.modem.user
            current_password = prev_cfg.modem.password
            current_ping = prev_cfg.modem.ping_address
            current_apn = prev_cfg.modem.apn

            os.chdir('/home/')
            os.chdir('..')
            os.chdir('/etc')

            for filename in filenames:
                try:
                    new_login = self.full_unit_config.modem.user
                    new_password = self.full_unit_config.modem.password
                    new_ping = self.full_unit_config.modem.ping_address
                    new_apn = self.full_unit_config.modem.apn

                    with open(f"{filename}.cfg", "r") as file:
                        modem_settings_file_data = file.read().splitlines()

                    current_modem_apn = modem_settings_file_data[2].split(",")
                    current_modem_apn[2] = new_apn

                    new_apn_string = ','.join(current_modem_apn)
                    modem_settings_file_data[2] = new_apn_string  # APN
                    if filename in [ModemConfig.MODEM_DEMON_WDIALIRZ, ModemConfig.MODEM_DEMON_RNDISIRZ]:
                        modem_settings_file_data[3] = f'user "{new_login}" password "{new_password}"'  # LOGIN PAROL
                    else:
                        modem_settings_file_data[3] = f'"{new_login}","{new_password}"'  # LOGIN PAROL
                    modem_settings_file_data[6] = f'{new_ping}'  # ping

                    if filename == ModemConfig.MODEM_DEMON_WTELIT:
                        lch = 'usbreset 1bc7:0036;sleep 5;busybox devmem 0x43CB0000 32 1;sleep 1; busybox devmem 0x43CB0000 32 0'
                        if len(modem_settings_file_data) == 7:
                            modem_settings_file_data.append(lch)
                        else:
                            modem_settings_file_data[7] = lch

                    with open(f"{filename}.cfg", "w") as file:
                        for line in modem_settings_file_data:
                            file.write(line + '\n')

                    if (current_login != new_login or current_password != new_password
                            or current_ping != new_ping or current_apn != new_apn):
                        subprocess.Popen(['sh', f'{settings.APP_PATH}/common/third_party/modem_demon_reboot.sh', f'{filename}'])
                except Exception as e:
                    pass
        except Exception as e:
            pass

    def _switch_fiber_optic(self, block_state: bool) -> tuple[bool, bool]:
        sfp = self.full_unit_config.alarm_config.sfp
        if not settings.DEBUG:
            DevMem(0x83C1_0000)[16] = rm0 = DevMem(0x83DF_0000)\
                .read(0)\
                .get_value() & 0b110 == 0 and sfp[0]

            DevMem(0x83C1_0000)[17] = rm1 = DevMem(0x83DF_0000) \
                .read(0)\
                .get_value() & 0b110_000 == 0 and sfp[1]

            rm0, rm1 = not rm0, not rm1
            if block_state:
                DevMem(0x83E0_0008)[2] = rm1
                # DevMem(0x83E1_0008)[2] = rm0
            else:
                DevMem(0x83E0_0008)[2] = rm0
                DevMem(0x83E1_0008)[2] = rm1
        else:
            rm0, rm1 = True, True

        return rm0, rm1

    async def _process_block_state(self) -> bool:
        control_api = ControlApi()

        block_state = self.full_unit_config.unit_configuration.unit_type == "FSC"
        await control_api.set_control_state('block_state', block_state)

        return block_state

    # async def off_rm(self, index: int):
    #     control_api = ControlApi()
    #     self.full_unit_config.set_rm_enable(index, False)
    #     await control_api.set_control_state(f'link_{index}_state', False)

    # async def off_sm(self, index: int):
    #     control_api = ControlApi()
    #     self.full_unit_config.service_channels[index].str.enabled = False
    #     await control_api.set_control_state(f'service_{index}_state', False)

    async def _process_relay_channel_configuration(self, block_state: bool, prev_cfg: FullUnitConfiguration, is_first_entry = False):
        dsp_api = DspApi()
        control_api = ControlApi()
        ad_api = Ad936xApi()
        rm_api = RelaysModuleApi()

        tx_ac_spead_hack_delta = [
            c.output_power_link.output_power - r.output_power_link.output_power
            for c, r in zip(self.full_unit_config.relay_channels[0].rtrs, prev_cfg.relay_channels[0].rtrs)
        ]

        link_ul_scale, link_dl_scale, \
            link_rtx_ul_offset, link_rtx_dl_offset, \
            services_states, links_output_agc, \
            service_rx_scale, service_rx_offset, \
            link_rx_out_offset, link_tx_out_offset = self._build_relay_offsets(block_state)

        if block_state:
            await dsp_api.set_dsp_config('SERVICE_RX_OFFSET', service_rx_offset)

            if not settings.DEBUG:
                mem = DevMem(address=0x83C1_0000, number_of_words=32, filename='/dev/mem', word_size=4)
                rtr_common = self.full_unit_config.relay_channels[1].rtr_common
                mem[5] = services_states[0] or rtr_common.optic_enabled[0]
                mem[6] = services_states[1] or rtr_common.optic_enabled[1]

            await dsp_api.set_dsp_config('SERVICE_RX_SCALE', service_rx_scale)
        else:
            await dsp_api.set_dsp_config('LINK_RX_OUT_OFFSET', link_rx_out_offset)
            await dsp_api.set_dsp_config('LINK_TX_OUT_OFFSET', link_tx_out_offset)

        await dsp_api.set_dsp_config('LINK_UL_SCALE', link_ul_scale)
        await dsp_api.set_dsp_config('LINK_DL_SCALE', link_dl_scale)
        await dsp_api.set_dsp_config('LINK_RTX_UL_OFFSET', link_rtx_ul_offset)
        await dsp_api.set_dsp_config('LINK_RTX_DL_OFFSET', link_rtx_dl_offset)

        enable_power = [False, False]
        for idx, state in enumerate(services_states):
            state_name = f'link_{idx}_state'
            if not (await control_api.get_control_state(state_name)) and services_states[idx]:
                enable_power[idx] = True

            await control_api.set_control_state(state_name, services_states[idx])

        time.sleep(0.666)
        frequencies = self._calc_relay_module_rx_tx_rf()
        """if not block_state:
            frequencies[0].rx_frequency, frequencies[0].tx_frequency = \
                frequencies[0].tx_frequency, frequencies[0].rx_frequency"""

        for i in range(2):
            config = await rm_api.get_configure(i)
            config.ten_v = services_states[0]
            config.pre_amp = services_states[0]
            config.rx_frequency = frequencies[i].rx_frequency
            config.tx_frequency = frequencies[i].tx_frequency
            config.commit = True
            await rm_api.set_configure(i, config)

        # rx_center_freq, tx_center_freq = self._calculate_method()
        # ad_api.set_center_frequency(0, DoubleFrequency(rx_frequency=rx_center_freq, tx_frequency=tx_center_freq))

        gains = self._calc_tx_rx_link_gain_in_config_fsr()
        if not self.full_unit_config.unit_configuration.ac_link_rx:
            await ad_api.set_gain(0, DoubleGain(gain_0=gains[0][0], gain_1=gains[1][0]))
        elif any(enable_power):
            config = await ad_api.get_config(0)
            _gains = [
                15 if enable_power[0] else config.gain_0,
                15 if enable_power[1] else config.gain_1,
            ]
            await ad_api.set_gain(0, DoubleGain(gain_0=_gains[0], gain_1=_gains[1]))

        if not self.full_unit_config.unit_configuration.ac_link_tx:
            await ad_api.set_attenuation(0, DoubleAttenuation(attenuation_0=70 - gains[0][1], attenuation_1=70 - gains[1][1]))
        elif any(enable_power):
            config = await ad_api.get_config(0)
            attenuation = [
                int(25 if enable_power[0] else config.attenuation_0),
                int(25 if enable_power[1] else config.attenuation_1),
            ]
            await ad_api.set_attenuation(0, DoubleAttenuation(attenuation_0=attenuation[0], attenuation_1=attenuation[1]))
        else:
            config = await ad_api.get_config(0)
            attenuation = [
                clamp(35, 7, config.attenuation_0 - tx_ac_spead_hack_delta[0]),
                clamp(35, 7, config.attenuation_1 - tx_ac_spead_hack_delta[1])
            ]
            await ad_api.set_attenuation(0, DoubleAttenuation(attenuation_0=attenuation[0], attenuation_1=attenuation[1]))

        mode = str(self.full_unit_config.unit_configuration.channel_3_and_4_config.value)
        if block_state is False and mode == 'LTE_GSM':
            for idx, channel in enumerate(self.full_unit_config.service_channels):
                if idx == 1:
                    await self._set_bandwidth('COE_UL_SV_LNK', idx, self.full_unit_config.service_channels[2].bandwidth)
                else:
                    await self._set_bandwidth('COE_UL_SV_LNK', idx, channel.bandwidth)
        else:
            for idx, channel in enumerate(self.full_unit_config.relay_channels):
                await self._set_bandwidth('COE_UL_SV_LNK', idx, channel.rtr_common.bandwidth.dl)

        for idx, channel in enumerate(self.full_unit_config.relay_channels):
            await self._set_bandwidth('COE_UL_LNK_LNK', idx, channel.rtr_common.bandwidth.ul)

        # async def poweroff_on_on_invalid_task(index: int):
        #     def check(alarm_index, only_max = False):
        #         alarm_info = ALARMS[alarm_index]
        #         if alarm_info.enabled is not None and alarm_info.enabled(self.alarms_context):
        #             return alarm_info.bounds.check(
        #                 self.alarms_context,
        #                 alarm_info.reader(self.alarms_context).value,
        #                 only_max
        #             )
        #
        #         return True
        #
        #     need_power_off = [False]
        #     def pass_alarm(alarm_index, only_max = False):
        #         alarm_info = ALARMS[alarm_index]
        #         alarm_name = alarm_info.alarm_code_description.name
        #
        #         if not check(alarm_index, only_max):
        #             need_power_off[0] = True
        #             self.raise_alarm(f'{alarm_name}OnPowerOn', True, alarm_info.reader(self.alarms_context).value)
        #         else:
        #             self.raise_alarm(f'{alarm_name}OnPowerOn', False, alarm_info.reader(self.alarms_context).value)
        #
        #     if is_first_entry:
        #         for _ in range(11):
        #             await asyncio.sleep(3)
        #
        #     await asyncio.sleep(0.1)
        #     await asyncio.sleep(0.1)
        #     await asyncio.sleep(14)
        #
        #     pass_alarm((5, 7)[index], True)
        #     pass_alarm((28, 29)[index])
        #     pass_alarm((89, 92)[index], True)
        #     pass_alarm((90, 93)[index], True)
        #     pass_alarm((91, 94)[index], True)
        #     pass_alarm((95, 96)[index])
        #     pass_alarm((97, 98)[index], True)
        #
        #     if need_power_off[0]:
        #         self.off_rm(index)
        #     else:
        #         if self.full_unit_config.unit_configuration.ac_link_rx:
        #             _gains = [15, 15]
        #         else:
        #             _gains = [gains[0][0], gains[1][0]]
        #
        #         if self.full_unit_config.unit_configuration.ac_link_tx:
        #             _attenuation = [25, 25]
        #         else:
        #             _attenuation = [70-gains[0][1], 70-gains[1][1]]
        #
        #         ad_api.set_gain(0, DoubleGain(gain_0=_gains[0], gain_1=_gains[1]))
        #         ad_api.set_attenuation(0, DoubleAttenuation(attenuation_0=attenuation[0], attenuation_1=attenuation[1]))
        #
        # if enable_power[0]:
        #     asyncio.create_task(poweroff_on_on_invalid_task(0))
        #
        # if enable_power[1]:
        #     asyncio.create_task(poweroff_on_on_invalid_task(1))

    async def _process_service_channels_configuration(self, block_state: bool, prev_cfg: FullUnitConfiguration, is_first_entry = False):
        dsp_api = DspApi()
        control_api = ControlApi()
        ad_api = Ad936xApi()
        attenuators_api = AttenuatorsApi()
        oc_api = OcbApi()

        serial_number = self.full_unit_config.unit_configuration.main_module.serial_number
        cviya = self.full_unit_config.unit_configuration.cviya
        cviya_info = _get_config_by_cviya(cviya)

        if not isinstance(cviya_info, ResponseMessage):
            for sm_index in range(4):
                control_reg_name = (
                     'service_0_ocb', 'service_1_ocb', 'service_2_ocb', 'service_3_ocb',
                )[sm_index]
                if sm_index in (2, 3):
                    sm_freq = self.full_unit_config.service_channels[sm_index].frequency
                else:
                    sm_freq = cviya_info.sms_freq[sm_index]

                sm_ocb_threshold = {
                    900: 3.8,
                    1800: 2.5,
                    2100: 2.2 #  TODO back to 2.5
                }.get(sm_freq, 2.6)
                await oc_api.set_ocb_threshold(control_reg_name, sm_ocb_threshold)

                if not block_state and serial_number == 'ЦВИЯ.464117.003-03 РБ':
                    save_config(f'sm/ad3_gain_{sm_index}', 10)
                elif self.technical_status.sm_telemetry[sm_index].is_rev_1_2.value and sm_freq == 1800:
                    save_config(f'sm/ad3_gain_{sm_index}', 0)
                elif self.technical_status.sm_telemetry[sm_index].is_rev_1_0.value:
                    save_config(f'sm/ad3_gain_{sm_index}', 10)
                elif sm_freq in (900, 2100):
                    save_config(f'sm/ad3_gain_{sm_index}', 0)
                else:
                    save_config(f'sm/ad3_gain_{sm_index}', 10)

        need_ac = False

        rx_offset, tx_offset, \
            base_station_power, \
            service_rx_scale, service_tx_scale, link_dl_scale, \
            service_tx_out_offset, \
            enable_service_28v_out, \
            services_out_power = self._build_service_offsets(
                block_state,
                [ch.type == ModuleType.GSM for ch in self.full_unit_config.service_channels]
            )

        enable_power = [False] * 4

        if block_state:
            await dsp_api.set_dsp_config('LINK_RX_OUT_OFFSET', rx_offset)
            await dsp_api.set_dsp_config('LINK_TX_OUT_OFFSET', -tx_offset)
        else:
            await dsp_api.set_dsp_config('SERVICE_RX_OFFSET', rx_offset)
            await dsp_api.set_dsp_config('SERVICE_TX_OFFSET', tx_offset)
            await dsp_api.set_dsp_config('SERVICE_RX_SCALE', service_rx_scale)
            await dsp_api.set_dsp_config('SERVICE_TX_SCALE', service_tx_scale)

            """if is_first_entry:
                ad_api.set_attenuation(0, DoubleAttenuation(attenuation_0=30, attenuation_1=30))
                ad_api.set_attenuation(0, DoubleAttenuation(attenuation_0=30, attenuation_1=30))"""

            for idx, enable in enumerate(enable_service_28v_out):
                state_name = f'service_{idx}_state'
                if not (await control_api.get_control_state(state_name)) and enable:
                    enable_power[idx] = True
                prev = await control_api.get_control_state(f'service_{idx}_state')
                await control_api.set_control_state(f'service_{idx}_state', enable)

                if not prev and enable:
                    need_ac = True

            await control_api.set_control_state('service_5v_state', any(enable_service_28v_out))

        service_channels = self.full_unit_config.service_channels
        no_zero_cast = lambda x: x if x > 100 else 100
        frequencies = [
            DoubleFrequency(
                            rx_frequency=no_zero_cast(service_channels[0].str.carrier_frequency.dl),
                            tx_frequency=no_zero_cast(service_channels[0].str.carrier_frequency.ul)
                            ),
            DoubleFrequency(
                            rx_frequency=no_zero_cast(service_channels[2].str.carrier_frequency.dl),
                            tx_frequency=no_zero_cast(service_channels[2].str.carrier_frequency.ul)
                            ),
        ]
        mode = str(self.full_unit_config.unit_configuration.channel_3_and_4_config.value)
        if not block_state:
            for i in range(2):
                frequencies[i].rx_frequency, frequencies[i].tx_frequency = \
                    frequencies[i].tx_frequency, frequencies[i].rx_frequency

        await ad_api.set_center_frequency(1, frequencies[0])
        if mode not in ['GSM', 'LTE_GSM']:
            await ad_api.set_center_frequency(2, frequencies[1])

        channels = self.full_unit_config.service_channels
        clamp_b = partial(clamp, 70, 1)
        gains = [
            DoubleGain(
                gain_0=clamp_b(channels[1].str.gain.dl) if block_state else clamp_b(channels[1].str.gain.ul),
                gain_1=clamp_b(channels[0].str.gain.dl) if block_state else clamp_b(channels[0].str.gain.ul)
            ),
            DoubleGain(
                gain_0=clamp_b(channels[3].str.gain.dl) if block_state else clamp_b(channels[3].str.gain.ul),
                gain_1=clamp_b(channels[2].str.gain.dl) if block_state else clamp_b(channels[2].str.gain.ul)
            )
        ]

        async def gain_control():
            for idx, gain in enumerate(gains):
                await ad_api.set_gain(idx + 1, gain)

        # match (self.full_unit_config.unit_configuration.ac_link_rx, block_state):
        #     case (False, _):
        #         gain_control()
        #     case (True, False):
        #         gain_control()
        #     case (True, True):
        #         pass

        await gain_control()

        chs = self.full_unit_config.service_channels
        attenuation1 = DoubleAttenuation(attenuation_0=70 - chs[1].str.gain.ul, attenuation_1=70 - chs[0].str.gain.ul) \
            if block_state else \
            DoubleAttenuation(attenuation_0=70 - chs[1].str.gain.dl, attenuation_1=70 - chs[0].str.gain.dl)

        attenuation2 = DoubleAttenuation(attenuation_0=70 - chs[3].str.gain.ul, attenuation_1=70 - chs[2].str.gain.ul) \
            if block_state else \
            DoubleAttenuation(attenuation_0=70 - chs[3].str.gain.dl, attenuation_1=70 - chs[2].str.gain.dl)

        async def attenuation_control():
            await ad_api.set_attenuation(1, attenuation1)
            await ad_api.set_attenuation(2, attenuation2)

        async def in_attenuation():
            attenuators_config = []
            for channel in channels:
                attenuators_config.append(0)
                attenuators_config.append(channel.attenuation.dl)

            await attenuators_api.set_attenuators(AttenuatorsConfig(values=attenuators_config))

        match (self.full_unit_config.unit_configuration.ac_service_tx, block_state):
            case (_, True):
                await attenuation_control()
                await in_attenuation()
            case (False, False):
                await attenuation_control()
            case (True, False):
                pass

        mode = str(self.full_unit_config.unit_configuration.channel_3_and_4_config.value)

        if block_state is False and mode == 'LTE_GSM':
            for idx, channel in enumerate(self.full_unit_config.relay_channels):
                if idx in [0, 3]:
                    await self._set_bandwidth('COE_DL_LNK_SV', idx, channel.rtr_common.bandwidth.dl)
                elif idx == 1:
                    if self.full_unit_config.service_channels[2].str.enabled:
                        await self._set_bandwidth('COE_DL_LNK_SV', idx,  self.full_unit_config.service_channels[2].bandwidth)
                    elif self.full_unit_config.service_channels[3].str.enabled:
                        await self._set_bandwidth('COE_DL_LNK_SV', idx, self.full_unit_config.service_channels[3].bandwidth)

        elif block_state and mode == 'LTE_GSM':
            for idx, channel in enumerate(self.full_unit_config.service_channels):
                if idx in [0, 1, 2]:
                    await self._set_bandwidth('COE_DL_LNK_SV', idx, channel.bandwidth)
                else:
                    if self.full_unit_config.relay_channels[idx].rtr_common.bandwidth.dl // 1_000_000 == 30:
                        await self._set_bandwidth('COE_DL_LNK_SV', idx, 30_000_000)
                    else:
                        await self._set_bandwidth('COE_DL_LNK_SV', idx, channel.bandwidth)
        else:
            for idx, channel in enumerate(self.full_unit_config.service_channels):
                await self._set_bandwidth('COE_DL_LNK_SV', idx, channel.bandwidth)

        if self.full_unit_config.service_channels[0].str.enabled and not self.full_unit_config.service_channels[1].str.enabled:
            bw0 = self.full_unit_config.service_channels[0].bandwidth
        elif not self.full_unit_config.service_channels[0].str.enabled and self.full_unit_config.service_channels[1].str.enabled:
            bw0 = self.full_unit_config.service_channels[1].bandwidth
        else:
            bw0 = max(self.full_unit_config.service_channels[0].bandwidth,
                      self.full_unit_config.service_channels[1].bandwidth)

        if self.full_unit_config.service_channels[2].str.enabled and not self.full_unit_config.service_channels[3].str.enabled:
            bw1 = self.full_unit_config.service_channels[2].bandwidth
        elif not self.full_unit_config.service_channels[2].str.enabled and self.full_unit_config.service_channels[3].str.enabled:
            bw1 = self.full_unit_config.service_channels[3].bandwidth
        else:
            bw1 = max(self.full_unit_config.service_channels[2].bandwidth,
                      self.full_unit_config.service_channels[3].bandwidth)

        if mode == 'GSM':
            bw1 = 10_000_000

        if not is_first_entry:
            if self.full_unit_config.service_channels[0].bandwidth != prev_cfg.service_channels[0].bandwidth or\
                    self.full_unit_config.service_channels[1].bandwidth != prev_cfg.service_channels[1].bandwidth:
                await ad_api.direct_write_bandwidth(1, int(bw0))
                save_config(f'ad/invalid_ndt_samples-0', True)
                save_config(f'ad/invalid_ndt_samples-1', True)

            if self.full_unit_config.service_channels[2].bandwidth != prev_cfg.service_channels[2].bandwidth or\
                    self.full_unit_config.service_channels[3].bandwidth != prev_cfg.service_channels[3].bandwidth:
                await ad_api.direct_write_bandwidth(2, int(bw1))
                save_config(f'ad/invalid_ndt_samples-2', True)
                save_config(f'ad/invalid_ndt_samples-3', True)
        else:
            await ad_api.direct_write_bandwidth(1, int(bw0))
            await ad_api.direct_write_bandwidth(2, int(bw1))

        for idx, channel in enumerate(self.full_unit_config.service_channels):
            if channel.str.enabled:
                self.set_service_bw(idx, channel.bandwidth, mode)

        # block_state == False and mode = 'LTE_GSM'

        mode = str(self.full_unit_config.unit_configuration.channel_3_and_4_config.value)
        max_arfsn = self.full_unit_config.service_channels[0].max_arfsn
        min_arfsn = self.full_unit_config.service_channels[0].min_arfsn
        nb_subchannels_2 = self.full_unit_config.service_channels[2].nb_subchannels.copy()
        nb_subchannels_3 = self.full_unit_config.service_channels[3].nb_subchannels.copy()
        nb_subchannels_2.enabled = self.full_unit_config.service_channels[2].str.enabled
        nb_subchannels_3.enabled = self.full_unit_config.service_channels[3].str.enabled
        await dsp_api.calc_adfreq_subch_offsets(
            nb_subchannels_2.dict(), nb_subchannels_3.dict(),
            mode,
            max_arfsn, min_arfsn,
            self.full_unit_config.service_channels[2].str.enabled,
            self.full_unit_config.service_channels[3].str.enabled
        )

        if mode in ['GSM', 'LTE_GSM']:
            tx, rx = (await ad_api.get_config(2)).tx_frequency, (await ad_api.get_config(2)).rx_frequency
            if not block_state:
                tx, rx = rx, tx

            self.full_unit_config.service_channels[2].str.carrier_frequency.ul = tx
            self.full_unit_config.service_channels[2].str.carrier_frequency.dl = rx

        await dsp_api.set_delays_config(self._update_extra_config(block_state))

        # async def poweroff_on_on_invalid_task(index: int):
        #     def check(alarm_index, only_max = False):
        #         alarm_info = ALARMS[alarm_index]
        #         if alarm_info.enabled is not None and alarm_info.enabled(self.alarms_context):
        #             return alarm_info.bounds.check(
        #                 self.alarms_context,
        #                 alarm_info.reader(self.alarms_context).value,
        #                 only_max
        #             )
        #
        #         return True
        #
        #     need_power_off = [False]
        #     def pass_alarm(alarm_index, only_max = False):
        #         alarm_info = ALARMS[alarm_index]
        #         alarm_name = alarm_info.alarm_code_description.name
        #
        #         if not check(alarm_index, only_max):
        #             need_power_off[0] = True
        #             self.raise_alarm(f'{alarm_name}OnPowerOn', True, alarm_info.reader(self.alarms_context).value)
        #         else:
        #             self.raise_alarm(f'{alarm_name}OnPowerOn', False, alarm_info.reader(self.alarms_context).value)
        #
        #     if is_first_entry:
        #         for _ in range(11):
        #             await asyncio.sleep(3)
        #
        #     await asyncio.sleep(0.1)
        #     await asyncio.sleep(0.1)
        #     await asyncio.sleep(14)
        #
        #     # pass_alarm((16, 18, 20, 22)[index])
        #     pass_alarm((17, 19, 21, 23)[index])
        #     pass_alarm((30, 31, 32, 33)[index], True)
        #     pass_alarm((69, 70, 71, 72)[index])
        #     pass_alarm((73, 74, 75, 76)[index], True)
        #     pass_alarm((77, 78, 79, 80)[index], True)
        #     pass_alarm((81, 82, 83, 84)[index], True)
        #     # pass_alarm((85, 86, 87, 88)[index], True)
        #
        #     ad_index = (1, 1, 2, 2)[index]
        #
        #     if need_power_off[0]:
        #         self.off_sm(index)
        #     else:
        #         ad_api.set_gain(ad_index, gains[index//2])
        #         if self.full_unit_config.unit_configuration.ac_service_tx:
        #             ad_api.set_attenuation(ad_index, DoubleAttenuation(attenuation_0=30, attenuation_1=30))
        #         else:
        #             if ad_index == 1:
        #                 ad_api.set_attenuation(1, attenuation1)
        #             elif ad_index == 2:
        #                 ad_api.set_attenuation(2, attenuation2)

        # for idx in range(4):
        #     if enable_power[idx]:
        #         asyncio.create_task(poweroff_on_on_invalid_task(idx))

        return need_ac

    def set_service_bw(self, index: int, bandwidth: float, mode: str):
        def _write_offset(addr: int, offset: float) -> None:
            rounder = 60_000_000 / 1024
            delta_theta = round((max(min(offset, 100), -100) * 1_000_000) // rounder * rounder * (2 ** 30) / 60_000_000)
            DevMem(address=addr) \
                .write(0, delta_theta)

        bw = bandwidth
        if index in [2, 3]:
            if mode == 'GSM':
                bw = 10_000_000
            elif mode == 'LTE_GSM':
                bw += 10_000_000

        bw /= 2_000_000
        bw += 2
        if not settings.DEBUG:
            address = 0x83E6_0000 + index * 4
            _write_offset(address, -bw)

    async def _set_bandwidth(self, name: str, index: int, bandwidth: float):
        dsp_api = DspApi()

        index_ = index + 1
        bw = round(bandwidth // 1_000_000)

        if bw in [5, 10, 15, 20, 30]:
            coe_name = f'{bw}MHz'
        else:
            coe_name = '5MHz'

        await dsp_api.set_typical_coe(name, index_, coe_name)

    def _build_relay_offsets(self, block_state: bool):
        def switch_set(config: DspConfig | None, f0, f1) -> None:
            if config is not None:
                config.c[idx] = f0() if block_state else f1()

        service_rx_scale = DspConfig() if block_state else None
        service_rx_offset = DspConfig() if block_state else None

        link_rx_out_offset = None if block_state else DspConfig()
        link_tx_out_offset = None if block_state else DspConfig()

        link_ul_scale = DspConfig()
        link_dl_scale = DspConfig()
        link_rtx_ul_offset = DspConfig()
        link_rtx_dl_offset = DspConfig()
        services_states = [False, False]
        links_output_agc = [OutputPowerLinkConfig(), OutputPowerLinkConfig()]

        for idx, channel in enumerate(self.full_unit_config.relay_channels):
            rmo0 = channel.rtr_common.rt_rxs_enabled[0] or channel.rtr_common.optic_enabled[0]
            rmo1 = channel.rtr_common.rt_rxs_enabled[1] or channel.rtr_common.optic_enabled[1]
            rtr_common = channel.rtr_common
            rt_rxs_all_enabled = rmo0 and rmo1

            has_optic = any(channel.rtr_common.optic_enabled)

            switch_set(service_rx_scale,
                       lambda: float(rmo0),
                       lambda: None)

            switch_set(link_rx_out_offset,
                       lambda: None,
                       lambda: rtr_common.frequency_offset.ul / 1_000_000.0)
            switch_set(link_tx_out_offset,
                       lambda: None,
                       lambda: -rtr_common.frequency_offset.ul / 1_000_000.0)

            switch_set(link_ul_scale,
                       lambda: float(rmo1),
                       lambda: float(rt_rxs_all_enabled and rtr_common.enabled.ul) * 1 if has_optic else 2)

            switch_set(link_dl_scale,
                       lambda: float(rtr_common.enabled.dl) * 1.0,
                       lambda: float(rtr_common.enabled.dl) * 1 if has_optic else 3)
            switch_set(link_rtx_ul_offset,
                       lambda: -rtr_common.frequency_offset.ul / 1_000_000.0,
                       lambda: -link_rx_out_offset.c[idx])
            switch_set(link_rtx_dl_offset,
                       lambda: rtr_common.frequency_offset.ul / 1_000_000.0,
                       lambda: -link_tx_out_offset.c[idx])

            switch_set(service_rx_offset,
                       lambda: link_rtx_ul_offset.c[idx],
                       lambda: None)

        channel = self.full_unit_config.relay_channels[1]
        for idx, rtr in enumerate(channel.rtrs):
            services_states[idx] = channel.rtr_common.rt_rxs_enabled[idx]
            links_output_agc[idx] = copy.copy(rtr.output_power_link)

        return link_ul_scale, link_dl_scale, \
            link_rtx_ul_offset, link_rtx_dl_offset, \
            services_states, links_output_agc, \
            service_rx_scale, service_rx_offset, \
            link_rx_out_offset, link_tx_out_offset

    def _calc_relay_module_rx_tx_rf(self):
        relay_channel = self.full_unit_config.relay_channels[0]

        frequencies: List[DoubleFrequency] = []
        for idx, rtr in enumerate(relay_channel.rtrs):
            frequencies.append(
                DoubleFrequency(
                    rx_frequency=clamp(7125000000, 6230000000, rtr.central_frequency.dl),
                    tx_frequency=clamp(7125000000, 6230000000, rtr.central_frequency.ul)
                )
            )

        return frequencies

    def _calc_tx_rx_link_gain_in_config_fsr(self) -> List[Tuple[int, int]]:
        relay_channel = self.full_unit_config.relay_channels[0]

        gains: List[Tuple[int, int]] = []
        for idx, rtr in enumerate(relay_channel.rtrs):
            gains.append((
                    clamp(70, 0, rtr.output_power_link.input_gain),
                    clamp(70, 0, rtr.output_power_link.output_gain)
            ))

        return gains

    # def _build_attenuator_values(self):
    #     att_config = AttenuatorsConfig()
    #     for ch in self.full_unit_config.service_channels:
    #         att_config.values.append(clamp(30, 0, ch.attenuation.ul))
    #
    #     return att_config

    def _build_service_offsets(self, block_state: bool, is_gsm: list[bool]):
        rx_offset = DspConfig()
        tx_offset = DspConfig()
        base_station_power = BSPower()

        service_rx_scale = None if block_state else DspConfig()
        service_tx_scale = None if block_state else DspConfig()
        link_dl_scale = None if block_state else DspConfig()
        service_tx_out_offset = None if block_state else DspConfig()
        enable_service_28v_out = None if block_state else [False, False, False, False]
        services_out_power = None if block_state else ServicesOutPower()

        for idx, channel in enumerate(self.full_unit_config.service_channels):
            rx_offset.c[idx] = channel.frequency_offset.ul / 1_000_000.0
            tx_offset.c[idx] = channel.frequency_offset.dl / 1_000_000.0

            base_station_power.service_cl_dl[idx] = channel.cable_loss.dl
            base_station_power.service_bs_power[idx] = channel.bs_power

            if not block_state:
                if idx in (2, 3) and self.full_unit_config.unit_configuration.channel_3_and_4_config == 'GSM':
                    service_tx_scale.c[idx] = float(channel.str.enabled) * 3
                else:
                    service_tx_scale.c[idx] = float(channel.str.enabled)

                service_rx_scale.c[idx] = float(channel.str.enabled)
                service_tx_out_offset.c[idx] = channel.frequency_offset.dl / 1_000_000.0
                enable_service_28v_out[idx] = channel.str.enabled
                services_out_power.service_out_power[idx] = channel.str.output_power

        return rx_offset, tx_offset, \
            base_station_power, \
            service_rx_scale, service_tx_scale, link_dl_scale, \
            service_tx_out_offset, \
            enable_service_28v_out, \
            services_out_power

    def _update_extra_config(self, block_state: bool):
        num_channels = len(self.full_unit_config.service_channels)
        new_config = Delays(sv_rx_delay=[0.0] * num_channels, sv_tx_delay=[0.0] * num_channels)

        if block_state:
            return new_config

        for idx, channel in enumerate(self.full_unit_config.service_channels):
            new_config.sv_tx_delay[idx] = clamp(125.0, 0.0, float(channel.delay.ul))
            new_config.sv_rx_delay[idx] = clamp(125.0, 0.0, float(channel.delay.dl))

        return new_config

    @staticmethod
    def _load_full_confi_from_services(config: FullUnitConfiguration):
        pass


    async def digital_tune_all(self):
        ad_api = Ad936xApi()
        await ad_api.digital_tune_all()

    async def get_ad_ul_dl(self) -> AdUlDl:
        block_state = self.full_unit_config.unit_configuration.unit_type == 'FSC'
        ad_api = Ad936xApi()
        rms = []
        for rm_index in range(2):
            rm = await ad_api.get_real_uldl(rm_index)
            rms.append(UlDl(ul=rm[0], dl=70.0 - rm[1]))

        sms = []
        for sm_index in range(2, 6):
            sm = await ad_api.get_real_uldl(sm_index)
            if block_state:
                sms.append(UlDl(ul=70 - sm[1], dl=sm[0]))
            else:
                sms.append(UlDl(ul=sm[0], dl=70 - sm[1]))

        sms[0], sms[1] = sms[1], sms[0]
        sms[2], sms[3] = sms[3], sms[2]

        return AdUlDl(
            rms=rms,
            sms=sms,
        )

    async def off_simulation_task(self):
        try:
            await asyncio.sleep(18.8 * 60)
        except asyncio.CancelledError:
            return

        if self.full_unit_config.unit_configuration.simulation_state:
            dsp_api = DspApi()
            await dsp_api.set_simulation_of_chain(False)
            self.full_unit_config.unit_configuration.simulation_state = False

        self._off_simulation_task = None






